Vue2-封装一个含所有表单控件且支持动态增减行列的表格组件

效果

1. 无编辑权限:显示普通表格

2. 有编辑权限:根据配置显示编辑控件

3. 可以动态新增行,也可以动态新增列 

核心代码

无权限情况的核心代码

      <!-- 无编辑权限时显示普通表格 --><el-tablev-if="!hasEditPermission"ref="table":border="border":key="tablekey"fitv-bind="$attrs":element-loading-text="loadingText":data="tableData":header-cell-style="headerCellStyles":cell-style="cellStyles"v-on="$listeners"><!-- 复选框 --><el-table-columnv-if="showSelection"column-key="selection"v-bind="$attrs"type="selection"width="35"align="center"fixed/><!-- 序号 --><el-table-columnv-if="showIndex"column-key="index"label="序号"type="index"width="55"align="left"fixed/><!-- 所有列可扩展 --><el-table-columnv-if="showExpand"type="expand"width="25"align="center"fixed><slotslot-scope="scope"name="expand":elCol="scope.column":row="scope.row":rowIndex="scope.$index"/></el-table-column><!-- 表头数据 --><el-table-columnv-for="(column, cIndex) in tableColumns":key="cIndex":align="column.align || 'center'":prop="column.prop":label="column.label":width="column.width":sortable="column.sortable || false":type="column.type":fixed="column.fixed || (cIndex === 0 && firstColumnFixed)"v-bind="column.attrs"v-on="column.listeners"><!-- 表头的自定义插槽, 使用 slotHeaderName 和 slotHeaderIcon 定义--><templatev-if="column.slotHeaderName || column.slotHeaderIcon"#header><!-- 表头自定义插槽--><slotv-if="column.slotHeaderName":name="column.slotHeaderName":row="column"/><span v-if="column.slotHeaderIcon">{{ column.label }}</span><!-- 表头图标插槽:使用表头图标,默认添加弹出层,弹出层内容插槽名称为:表头插槽名称 + PopContent --><el-popoverv-if="column.slotHeaderIcon"class="header-slot-popup"placement="bottom":title="column.slotHeaderIconPopTitle || column.label":width="column.slotHeaderPopWidth"trigger="click"><div class="filter-content"><slotv-if="column.slotHeaderIcon"class="filter-slot":name="column.slotHeaderPopName + 'PopContent'"/></div><!-- 列对象中添加 slotHeaderIcon 属性,表示使用头部图标插槽--><svg-iconv-if="column.slotHeaderIcon"slot="reference"class="header-icon-slot":class="column.slotHeaderIconClass":icon-class="column.slotHeaderIcon"@click="headerIconClick(column)"/></el-popover></template><!-- 插入自定义模板到列中 --><slotv-if="column.textSlot"slot-scope="scope":name="column.textSlotName || 'slotTextCol'":colConf="column":elCol="scope.column":row="scope.row":rowIndex="scope.$index":colIndex="cIndex":rowspan="column.rowspan"/><!-- 扩展列插槽 --><template v-else-if="column.expandSlot"><slotv-if="column.expandSlot":name="column.expandSlot || 'slotExpend'":colConf="column":elCol="scope.column":row="scope.row":rowIndex="scope.$index":colIndex="cIndex":rowspan="column.rowspan"/></template><!--列内容插槽 --><template v-else slot-scope="scope"><div class="content"><el-checkboxv-if="column.checkbox":value="checked(scope.row, column)"@change="checkboxChange($event, scope.row, column)">{{ scope.row[column.prop] }}</el-checkbox><el-tooltipv-elseclass="tootip-container"effect="dark"placement="top":disabled="!column.tooltip"><template slot="content"><!-- 字符串长度大于 80 格式化 --><spanv-if="String(scope.row[column.prop]).length > 80":class="{ 'cursor-pointer hoveTitle': column.tooltip }"v-html="formateTooltip(String(scope.row[column.prop]))"/><span v-else>{{ String(scope.row[column.prop]) }}</span></template><p><span:class="{ 'cursor-pointer hoveTitle': column.tooltip }">{{column.formatter(scope.row,scope.column,scope.row[column.prop],scope.$index)}}</span></p></el-tooltip></div></template></el-table-column><!-- 固定列数据 --><el-table-columnv-if="showButtons"column-key="buttons":label="fixButConfig.label":align="fixButConfig.align":fixed="fixButConfig.fixed":width="fixButConfig.width"><slotslot="default"slot-scope="scope"name="buttons":$index="scope.$index":row="scope.row":column="scope.column"/></el-table-column><!-- 默认插槽 --><slot /></el-table>

 有权限情况的核心代码

      <!-- 有编辑权限时显示可编辑表格 --><el-form v-else :model="formModel" :rules="rules" :ref="formRef"><el-tableref="table":border="border":key="tablekey"fitv-bind="$attrs":element-loading-text="loadingText":data="tableData":header-cell-style="headerCellStyles":cell-style="cellStyles"v-on="$listeners"><!-- 复选框 --><el-table-columnv-if="showSelection"column-key="selection"v-bind="$attrs"type="selection"width="35"align="center"fixed/><!-- 序号 --><el-table-columnv-if="showIndex"column-key="index"label="序号"type="index"width="55"align="left"fixed/><!-- 删除行 --><el-table-columnv-if="hasEditPermission && showRemoveRow"label=""width="50"align="center"fixed><template slot-scope="scope"><el-buttonicon="el-icon-remove-outline"size="medium"type="text"circle:disabled="data.length <= 1"@click="removeRow(scope.$index)"></el-button></template></el-table-column><!-- 可编辑列 --><el-table-columnv-for="(column, cIndex) in tableColumns":key="cIndex":align="column.align || 'center'":prop="column.prop":label="column.label":width="column.width"v-bind="column.attrs"><!-- 表头的自定义插槽, 使用 slotHeaderName 和 slotHeaderIcon 定义--><templatev-if="column.slotHeaderName || column.slotHeaderIcon"#header><!-- 表头自定义插槽--><slotv-if="column.slotHeaderName":name="column.slotHeaderName":row="column"/><span v-if="column.slotHeaderIcon">{{ column.label }}</span><!-- 表头图标插槽:使用表头图标,默认添加弹出层,弹出层内容插槽名称为:表头插槽名称 + PopContent --><el-popoverv-if="column.slotHeaderIcon"class="header-slot-popup"placement="bottom":title="column.slotHeaderIconPopTitle || column.label":width="column.slotHeaderPopWidth"trigger="click"><div class="filter-content"><slotv-if="column.slotHeaderIcon"class="filter-slot":name="column.slotHeaderPopName + 'PopContent'"/></div><!-- 列对象中添加 slotHeaderIcon 属性,表示使用头部图标插槽--><svg-iconv-if="column.slotHeaderIcon"slot="reference"class="header-icon-slot":class="column.slotHeaderIconClass":icon-class="column.slotHeaderIcon"@click="headerIconClick(column)"/></el-popover></template><template #header v-else><span :class="{ 'required-label': isRequiredColumn(column) }">{{ column.label }}</span></template><template slot-scope="scope"><el-form-item:prop="`[${scope.$index}].${column.prop}`":rules="column.rules":style="column.style":class="column.className"><!-- 只读字段 --><template v-if="!column.type && !column.slot"><slotv-if="column.textSlot":name="column.textSlotName || 'slotTextCol'":colConf="column":elCol="scope.column":row="scope.row":rowIndex="scope.$index":colIndex="cIndex":rowspan="column.rowspan"/><el-tooltipv-elsev-bind="column.attrs"class="tootip-container"effect="dark"placement="top":disabled="!column.tooltip"><template slot="content"><!-- 字符串长度大于 80 格式化 --><spanv-if="String(scope.row[column.prop]).length > 80":class="{ 'cursor-pointer hoveTitle': column.tooltip }"v-html="formateTooltip(String(scope.row[column.prop]))"/><span v-else>{{ String(scope.row[column.prop]) }}</span></template><p><span:class="{ 'cursor-pointer hoveTitle': column.tooltip }">{{column.formatter(scope.row,scope.column,scope.row[column.prop],scope.$index)}}</span></p></el-tooltip></template><!-- 可编辑字段-根据列类型渲染不同表单控件 --><template v-else><el-inputv-if="column.type === $const.DialogCompType.input"v-model="data[scope.$index][column.prop]"class="item-inputs":placeholder="placeholderFormate(column)":style="{ width: column.width || '100%' }"v-bind="column.attrs"v-on="column.listeners"/><remote-search-selectorv-else-if="column.type === $const.DialogCompType.remoteSearchSelector":init-value="data[scope.$index][column.prop]"class="item-inputs":placeholder="placeholderFormate(column)"v-bind="column.attrs":options="column.optionList":show-value="column.showValue":remote-methods="column.remoteMethod":style="{ width: column.width || '100%' }"v-on="column.listeners"@valueChange="remoteSearchValueChage($event, data[scope.$index], column)"/><el-input-numberv-else-if="column.type === $const.DialogCompType.number"v-model="data[scope.$index][column.prop]"class="item-inputs":style="{ width: column.width || '100%' }":placeholder="placeholderFormate(column)"v-bind="column.attrs"v-on="column.listeners"/><el-inputv-else-if="column.type === $const.DialogCompType.textarea"v-model="data[scope.$index][column.prop]"class="item-inputs"type="textarea":style="{ width: column.width || '100%' }":placeholder="placeholderFormate(column)"v-bind="column.attrs":autosize="column.attrs && column.attrs.autosize? column.attrs.autosize: true"v-on="column.listeners"/><el-date-pickerv-else-if="column.type === $const.DialogCompType.datePicker"v-model="data[scope.$index][column.prop]"class="item-inputs":type="(column.attrs && column.attrs.type) || 'date'":placeholder="placeholderFormate(column)":style="{ width: column.width || '100%' }"v-bind="column.attrs"v-on="column.listeners"/><el-date-pickerv-else-if="column.type === $const.DialogCompType.dateTime"v-model="data[scope.$index][column.prop]"class="item-inputs"type="datetime":placeholder="placeholderFormate(column)":style="{ width: column.width || '100%' }"v-bind="column.attrs"v-on="column.listeners"/><date-picker-wrapv-else-if="column.type === $const.DialogCompType.datePickerColor":date="data[scope.$index][column.prop]"class="item-inputs":placeholder="placeholderFormate(column)":style="{ width: column.width || '100%' }"v-bind="column.attrs"v-on="column.listeners"/><el-date-pickerv-else-if="column.type === $const.FormCompType.daterange"v-model="data[scope.$index][column.prop]":type="(column.attrs && column.attrs.type) || 'daterange'":placeholder="placeholderFormate(column)":style="{ width: column.width || '100%' }":picker-options="{ disabledDate: daterangeDisabledDate }"v-bind="column.attrs"v-on="column.listeners"/><el-date-pickerv-else-if="column.type === $const.FormCompType.datetimerange"v-model="data[scope.$index][column.prop]"type="datetimerange":placeholder="placeholderFormate(column)":style="{ width: column.width || '100%' }"v-bind="column.attrs"v-on="column.listeners"/><el-switchv-else-if="column.type === $const.DialogCompType.switch"v-model="data[scope.$index][column.prop]"v-bind="column.attrs"v-on="column.listeners"/><el-dropdownv-else-if="column.type === $const.DialogCompType.dropdown"v-bind="column.attrs"v-on="column.listeners"><el-button v-bind="column.dropdownButtonsAttrs">{{ column.dropdownLabel }}<i class="el-icon-arrow-down el-icon--right" /></el-button><el-dropdown-menu slot="dropdown"><el-dropdown-itemv-for="dItem in column.dropdownItem":key="dItem.label"v-bind="dItem.attrs"v-on="dItem.listeners">{{ dItem.label }}</el-dropdown-item></el-dropdown-menu></el-dropdown><selector-wrapv-else-if="column.type === $const.DialogCompType.select":item="column":data="data[scope.$index]"v-bind="column.attrs"v-on="column.listeners"/><el-radio-groupv-else-if="column.type === $const.DialogCompType.radio"v-model="data[scope.$index][column.prop]"class="item-inputs":style="{ width: column.width || '100%' }"v-bind="column.attrs"v-on="column.listeners"><el-radiov-for="option in column.optionList":key="option.id || option.value":label="option.value"v-bind="option.attrs"v-on="option.listeners">{{ option.label }}</el-radio></el-radio-group><el-sliderv-else-if="column.type === $const.DialogCompType.slider"v-model="data[scope.$index][column.prop]"class="item-inputs"v-bind="column.attrs"v-on="column.listeners"/><el-alertv-else-if="column.type === $const.DialogCompType.alert"v-bind="column.attrs"/><el-dividerv-else-if="column.type === $const.DialogCompType.divider"v-bind="column.attrs"><span v-if="column.tips">{{ column.tips }}</span></el-divider><file-uploadv-else-if="column.type === $const.DialogCompType.files"class="item-inputs"v-bind="column.attrs"v-on="column.listeners"/><divv-else-if="column.type === $const.DialogCompType.buttons"class="dialog-buttons":style="{display: 'flex','justify-content':buttonsAlign[column.align] || 'flex-end',}"><el-buttonv-for="but in column.buttons":key="but.label":type="but.type"v-bind="but.attrs"size="small"@click="submitValidate(but.validate, but.reset, but.event)">{{ but.label }}</el-button></div><tinymcev-else-if="column.type === $const.DialogCompType.editor"v-model="data[scope.$index][column.prop]":height="column.height"/><slotv-else-if="column.slot":name="column.slotName || 'slotCol'":colConf="column":elCol="scope.column":row="scope.row":rowIndex="scope.$index":colIndex="cIndex":rowspan="column.rowspan"/></template></el-form-item></template></el-table-column><!-- 增加列 --><el-table-columnv-if="hasEditPermission && showAddColumn"label=""width="45"align="center"fixed='right'><template #header><el-buttonicon="el-icon-circle-plus-outline"size="medium"type="text"circle@click="addColumn"></el-button></template></el-table-column><!-- 操作列 --><el-table-columnv-if="showButtons"column-key="buttons":label="fixButConfig.label":align="fixButConfig.align":fixed="fixButConfig.fixed":width="fixButConfig.width"><slotslot="default"slot-scope="scope"name="buttons":$index="scope.$index":row="scope.row":column="scope.column"/></el-table-column></el-table></el-form><!-- 在表格下方添加操作按钮 --><div v-if="hasEditPermission" class="row-actions"><el-buttonv-if="showAddRow"type="text"size="medium "@click="addRow"icon="el-icon-circle-plus-outline">增加行</el-button><el-buttonv-if="showSave"type="text"size="medium "@click="saveData"icon="el-icon-circle-check">保存</el-button></div>

增减行列核心方法

    // 增加行addRow() {const newRow = {};this.columns.forEach((col) => {newRow[col.prop] = ""; // 初始化空值});this.tempData = [...this.tempData, newRow]; // 更新 tempDatathis.$emit("update:data", this.tempData);// 不再强制刷新整个表格// this.tablekey += 1;},// 验证表单validateForm() {return new Promise((resolve) => {this.$refs[this.formRef].validate((valid) => {if (valid) {resolve(true);} else {this.$message.error("请检查表单填写是否正确");resolve(false);}});});},// 删除行removeRow(index) {this.tempData.splice(index, 1);this.$emit("update:data", [...this.tempData]);// 不再强制刷新整个表格// this.tablekey += 1;},// 保存数据async saveData() {const isValid = await this.validateForm();this.$emit("update:data", [...this.tempData]);return isValid;},// 比较列配置是否相等isColumnsEqual(newColumns, oldColumns) {if (newColumns.length !== oldColumns.length) return false;return newColumns.every((newCol, index) => {const oldCol = oldColumns[index];return JSON.stringify(newCol) === JSON.stringify(oldCol);});},// 新增列async addColumn() {try {this.$emit('add-column', this.columns);this.$nextTick(() => {if (this.$refs.table) {this.$refs.table.doLayout();}});} catch (error) {this.$message.error('新增列操作出错,请稍后重试');}},

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

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

相关文章

网络原理 - TCP/IP(一)

目录 1. 应用层&#xff1a;用户与网络的 “交互窗口” 1.1 应用层协议&#xff1a;规范交互的 “通用语言” 1.2 自定义协议&#xff1a;适配特殊需求的 “专属规则” 1.3 应用层数据格式&#xff1a;让数据 “说得明白” 1.3.1 XML&#xff1a;结构化但繁琐的 “老…

Orange的运维学习日记--16.Linux时间管理

Orange的运维学习日记–16. Linux时间管理 文章目录Orange的运维学习日记--16. Linux时间管理系统与硬件时钟时钟类型对比查看内核支持的时钟源本地时间调整使用 date 查看与设置一次性同步&#xff1a;ntpdate同步到硬件时钟&#xff1a;hwclock基于 systemd 的 timedatectl交…

Git 与 GitHub 的对比与使用指南

Git 与 GitHub 的对比与使用指南 在软件开发中&#xff0c;Git 和 GitHub 是两个密切相关但本质不同的工具。下面我将逐步解释它们的定义、区别、核心概念以及如何协同使用&#xff0c;确保内容真实可靠&#xff0c;基于广泛的技术实践。 1. 什么是 Git&#xff1f; Git 是一个…

20250726-4-Kubernetes 网络-Service DNS名称解析_笔记

一、Service DNS名称 1. 例题:通信需求 通信场景:项目A中的Pod需要与项目B中的Pod进行通信,直接使用Pod IP不可行,因为Pod IP会随着Pod生命周期变化。 解决方案:通过Service提供的稳定IP地址进行通信,不受Pod重建、扩容/缩容等操作影响。 2. CoreDNS介绍  基本功能…

vscode 登录ssh记住密码直接登录设置

第一种情况在系统已经生成密钥对的情况下&#xff1a;点击这里的设置第二步&#xff1a;第三步&#xff1a;没有填写的给填写一下第四步骤&#xff1a;保存后进入选择这个点开第五步&#xff1a;去Linux终端下输入这个命令就OK了echo "ssh-rsa内容" >> ~/.ssh/…

Nginx 动静分离配置(详细版)

本文介绍了Nginx 动静分离相关配置&#xff0c;主要包括了配置文件创建、配置示例、配置原理解析以及重新启用配置文件等等 本文目录1. 创建 Nginx 配置文件2. 配置示例3. 配置原理解析4. 启用配置文件并重新加载 Nginx1. 创建 Nginx 配置文件 在 /etc/nginx/sites-available …

C# CAN通信上位机系统设计与实现

C# CAN通信上位机系统设计与实现 C# CAN通信上位机程序&#xff0c;支持多种CAN适配器&#xff0c;提供数据收发、协议解析、数据可视化等功能。 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; usi…

Ubuntu20.04子系统

常用 # 导出分发版到 E盘 wsl --export Ubuntu-20.04 E:\wsl-ubuntu20.04.tar # 注销原有分发版 wsl --unregister Ubuntu-20.04 # 导入到 E盘的新路径&#xff08;例如 E:\WSL\Ubuntu-20.04&#xff09; wsl --import Ubuntu-20.04 E:\WSL\Ubuntu-20.04 E:\wsl-ubuntu20.04.t…

【设计模式】状态模式 (状态对象(Objects for States))

状态模式&#xff08;State Pattern&#xff09;详解一、状态模式简介 状态模式&#xff08;State Pattern&#xff09; 是一种 行为型设计模式(对象行为型模式)&#xff0c;它允许一个对象在其内部状态改变时改变其行为。换句话说&#xff0c;对象看起来好像修改了它的类。 你…

工业前端组件库重构心法:如何让开发效率提升60%的交互模块设计逻辑

工业前端组件库重构心法&#xff1a;如何让开发效率提升60%的交互模块设计逻辑内容摘要在工业项目开发中&#xff0c;前端组件库是提升开发效率的关键。然而&#xff0c;许多团队的组件库存在设计不合理、维护困难等问题&#xff0c;导致开发效率低下。如果能够重构组件库&…

leetcode 74. 搜索二维矩阵

二分查找经典题目&#xff1b;根据矩阵的特点&#xff0c;不需要把矩阵拉成一维&#xff0c;二维转成一维映射关系为a[i]matrix[⌊i//n⌋][i%n]&#xff1b;然后开始二分查找&#xff0c;一直二分的收缩区间&#xff1b;class Solution:def searchMatrix(self, matrix: List[Li…

26考研英语词汇的逻辑笔记(Unit31-43)

行为UNIT 31词汇数量&#xff1a;274 词群数量&#xff1a;16 词群逻辑&#xff1a;行为举止 | 行为标准与原则 给予、收回 | 接受、允许、让步、拒绝 促进、鼓励 | 支持、帮助、资助 破坏相关 | 错误、改正 阻碍、打扰相关 | 禁止、阻止、限制 值得、有利、不利相关 | 有意、故…

Lua(数据库访问)

Lua 数据库访问方法Lua 本身不提供内置的数据库访问功能&#xff0c;但可以通过第三方库实现与多种数据库的交互。以下是常见的 Lua 数据库访问方法&#xff1a;使用 LuaSQL 库LuaSQL 是一个轻量级数据库访问库&#xff0c;支持多种数据库后端&#xff08;MySQL、PostgreSQL、S…

在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE

在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE 文章目录 在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE 1. 前置要求 1.1. 硬件信息(例) 1.2. 准备工作 2. 安装步骤 2.1. 登录 iDRAC9 2.2. 启动虚拟控制台 2.3. 挂载 Proxmox VE ISO 2.4. 设置服务器从虚拟…

window下MySQL安装(三)卸载mysql

window下MySQL安装&#xff08;三&#xff09;卸载mysql 卸载mysql数据库&#xff0c;停止服务&#xff0c;备份文件&#xff0c;删除mysql文件。结束。 停止mysql服务 以管理员身份打开命令提示符或 PowerShell&#xff1a; net stop <服务名称> 示例&#xff1a;net st…

Elasticsearch 深度分页问题与 `search_after` 解决方案

1. 引言 主题&#xff1a;介绍 Elasticsearch 深度分页问题的背景&#xff0c;强调其在处理大规模数据集时的性能瓶颈。核心问题&#xff1a;传统 from/size 分页方式在深层分页&#xff08;例如第500页&#xff09;时&#xff0c;因需要加载和丢弃大量文档&#xff0c;导致内存…

Spring Boot 2整合MyBatis Plus详细指南

1. 环境准备Spring Boot版本&#xff1a;2.x&#xff08;推荐2.7.x&#xff09;MyBatis Plus版本&#xff1a;3.5.x&#xff08;兼容Spring Boot 2&#xff09;数据库&#xff1a;MySQL 8.0&#xff08;其他数据库需调整驱动&#xff09;2. 创建项目并添加依赖在pom.xml中添加核…

Docker镜像导入解析:docker import vs docker load

本文通过Busybox镜像的实战演示&#xff0c;深入剖析两个易混淆命令的技术原理与适用场景一、核心区别速览特性docker importdocker load输入来源容器文件系统快照(docker export输出)完整镜像归档(docker save输出)保留信息仅文件内容完整镜像(层/历史/配置/标签)生成镜像结构…

Android 解决键盘遮挡输入框

本文目录 点击直达Android 解决键盘遮挡输入框代码实现使用注意最后我还有一句话要说梧桐叶上三更雨&#xff0c;叶叶声声是别离。Android 解决键盘遮挡输入框 在安卓中通常可以通过添加android:windowSoftInputMode"adjustResize|stateHidden"的方式来让键盘顶起布…

热门JavaScript库“is“等软件包遭npm供应链攻击植入后门

轻量级 JavaScript 实用工具库 "is" 是 NPM 平台上的热门项目&#xff0c;每周下载量超过 220 万次。然而在 2025 年 7 月 19 日&#xff0c;该库开发者遭遇钓鱼攻击导致账户凭证泄露&#xff0c;攻击者借此发布了包含远程代码执行后门的恶意版本。钓鱼攻击入侵开发者…