vue3 + Element Plus动态生成表格,并实现内容可编辑


前言

第一个思路得自于网友的讨论,将单元格数据转对象,通过添加控制属性实现编辑状态与显示状态的切换。尽管思路简单,但实现的过程有些看着头大,不够简洁优美。采用这种方法的话,建议浏览了解后自行理清思路实现。

前段时间看到同事在类似问题上找了一个插件,简单得通过一个输入框就实现了对所有数据的编辑。仔细想想,只要能实现数据的“定位”,这种思路在实现过程上显得更加友好。

注意,示例基于 vue3 & element-plus@1.1.0-beta.12,高版本的 element-plus 中图标引入方式不同

代码仓库:editable-table
Github Page
在线演示 - 方案一
在线演示 - 方案二

注意:示例项目及文章写于2020年,基于 vue2 & element-ui,后来更新至 vue3 & element-plus,老版本在分支vue2上。由于只是demo,介绍解决方案方便理解与讨论,上生产环境的话肯定存在一些bug,有些必要的会修复,不必要的还请大家自行本地调试😉


一、思路一:单元格数据转对象

将单元格数据转换成对象,添加show属性,控制/切换显示与编辑模式

最终效果如下,可自由编辑表头、数据单元格,增删行列,并保持数据绑定

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

1.1 首先最基本的,根据数据生成固定表项的表格

从官网摘个Demo过来:

tableData: [
	{date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄'},
	{date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄'}
]
<el-table :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="日期" width="180"></el-table-column>
    <el-table-column prop="name" label="姓名" width="180"></el-table-column>
    <el-table-column prop="address" label="地址"></el-table-column>
</el-table>

在这里插入图片描述

1.2 动态生成表项

常用的写法:

columnList: [
  { prop: "name", label: '姓名' },
  { prop: "age", label: '年龄' },
  { prop: "city", label: '城市' },
  { prop: "tel", label: '电话' }
],
testDatas: [
  {No: 1, name: '张三', age: 24, city: '广州', tel: '13312345678'},
  {No: 2, name: '李四', age: 25, city: '九江', tel: '18899998888'},
  {No: 3, name: '王五', age: 26, city: '六盘水', tel: '13600001111'},
  {No: 4, name: '赵二', age: 27, city: '菏泽', tel: '13145209420'},
]
<el-table :data="testDatas" border stripe style="width: 100%">
  <el-table-column type="index" label="No." :width="50"/>
  <el-table-column
    v-for="col in columnList"
    :prop="col.prop"
    :label="col.label"
    :key="col.prop"
  />
</el-table>

在这里插入图片描述

1.3 单元格可编辑

网上早有网友讨论过这个功能,这里采用的是将单元格数据转换成对象,添加属性show来控制其在文字与输入框间切换。听起来就不想下手,又要转换数据格式了。。

// 表项(头),以键(prop)值(label)存储表头,值可改变,键不变用以绑定数据
columnList: [
  { prop: "name", label: 'name', show: true },
  { prop: "age", label: 'age', show: true },
  { prop: "city", label: 'city', show: true },
  { prop: "tel", label: 'tel', show: true }
],
// 数据
testDatas: [{
  name: { content: '张三', show: true },
  age: { content: 24, show: true },
  city: { content: '广州', show: true },
  tel: { content: '13312345678', show: true }
},{
  name: { content: '李四', show: true },
  age: { content: 25, show: true },
  city: { content: '九江', show: true },
  tel: { content: '18899998888', show: true }
}],
<div class="tb-container" ref="tbContainerRef">
  <el-table :data="testDatas" border style="width: 100%">
    <!-- 额外添加的编号项(可删除) -->
    <el-table-column v-if="columnList.length > 0" type="index" :label="'编号'" :width="50"></el-table-column>
    <!-- 自定义表项 -->
    <el-table-column v-for="column in columnList" :key="column.prop">
      <!-- 自定义表头 -->
      <template #header>
        <!-- 段落:show为true -->
        <p v-show="column.show" @dblclick="$event => handleEdit(col, $event.target)">
          {{column.label}} 
          <i class="el-icon-edit-outline" @click="$event => handleEdit(col, $event.target.parentNode)"></i>
        </p>
        <!-- 输入框:show为false -->
        <el-input
          size="mini"
          v-show="!column.show"
          v-model="column.label"
          @blur="column.show = true">
        </el-input>
      </template>

      <!-- 自定义表项/单元格内容 -->
      <template #default="{ row }">
        <!-- 双击文字或点击修改图标以更改"show"属性 -->
        <!-- row为元数据,column.col为该列的'键' -->
        <p v-show="row[column.prop].show" @dblclick="$event => handleEdit(row[col.prop], $event.target)">
          {{row[column.prop].content}} 
          <i class="el-icon-edit-outline" @click="$event => handleEdit(row[col.prop], $event.target.parentNode)"/>
        </p>
        <!-- 失去焦点时更改"show"属性,显示文本 -->
        <el-input
          type="textarea"
          :autosize="{minRows:2,maxRows:4}"
          v-show="!row[column.prop].show"
          v-model="row[column.prop].content"
          @blur="row[column.prop].show=true"
        />
      </template>
    </el-table-column>
  </el-table>
</div>
// ...
methods: {
  /**
   * 表头/单元格编辑处理:切换编辑输入框/文本输入框,自动聚焦
   *
   * @param {Object} cell - The cell object to edit.
   * @param {HTMLElement} pEl - The parent element of the cell.
   */
  handleEdit(cell, pEl) {
    const editIputEl = Array.from(pEl.nextSibling.childNodes).find(n => ['INPUT','TEXTAREA'].includes(n.tagName))
    cell.show = false
    editIputEl && this.$nextTick(() => {
      editIputEl.focus()
    })
  },
}

在这里插入图片描述

1.4 插入、删除一行或一列数据

做到第三点,这一步就没什么难度了,无非就是更改数组
想要实现对每一行每一列的操作,要用到Element表格插件提供的一些属性方法

  • 行的 className 的回调方法:row-class-name给行加下标(非常关键,“定位”单元格的基础)
  • 列的index属性(非常关键,“定位”单元格的基础)
  • 编辑框的控制与数据绑定
    • @header-contextmenu:表头右键事件
    • @row-contextmenu:数据行右键事件
<div class="tb-container" ref="tbContainerRef">
  <!-- 表格 -->
  <el-table
    :data="testDatas"
    @header-contextmenu="(column, $event) => rightClick(null, column, $event)"
    @row-contextmenu="rightClick"
    :row-class-name="tableRowClassName"
  >
    <el-table-column v-if="columnList.length > 0" type="index" :label="'编号'" :width="50"></el-table-column>
    <el-table-column v-for="(column, idx) in columnList" :key="idx" :index="idx">
      <!-- ... -->
    </el-table-column>
  </el-table>
  
  <!-- 表头右键菜单 -->
  <div v-show="showMenu" id="contextmenu">
    <i class="el-icon-circle-close hideContextMenu" @click="showMenu = false"></i>
    <el-button size="mini" type="primary">功能</el-button>
  </div>
</div>
// 添加表格行下标
tableRowClassName({row, rowIndex}) {
  row.row_index = rowIndex
},
rightClick(row, column, $event) {
  // 阻止浏览器自带的右键菜单弹出
  $event.preventDefault()
  if(column.index == null) return
  // 表格容器的位置
  const { x: tbX, y: tbY } = this.$refs.tbContainerRef.getBoundingClientRect()
  // 当前鼠标位置
  const { x: pX, y: pY } = $event
  // 定位菜单
  const ele = document.getElementById('contextmenu')
  ele.style.top = pY - tbY - 6 + 'px'
  ele.style.left = pX - tbX - 6 + 'px'
  this.showMenu = true
},

在这里插入图片描述

1.5 完整代码

Git链接
在线演示

1.6 vue2的坑:“列”变化出现的监听问题

vue3可跳过

vue2开发中,新增列的时候,要为所有行添加新的对象,通过一般的添加对象属性是无法被vue监听到的。具体参考这篇文章:关于vue无法侦听数组及对象属性的变化的解决方案

addColumn(idx) { // 新增列
  var obj = {col: 'col_' + this.count_col++, txt: '', show: true} // 新增列对象
  this.testDatas.map(p => {
    _this.$set(p, obj.col, {content: '', show: true})
    //	p[obj.col] = {content: '', show: true}
  })
}

二、思路二:精准定位编辑数据

双击修改数据:
在这里插入图片描述

右键编辑行列:
在这里插入图片描述

这个思路的实现代码很简洁清晰,就不像上一个那么详细写了

相关要点:

  • el-table的单元格双击事件cell-dblclick
  • 行的 className 的回调方法:row-class-name给行加下标(非常关键,“定位”单元格的基础)
  • 列的index属性(非常关键,“定位”单元格的基础)
  • 编辑框的控制与数据绑定
    • @header-contextmenu:表头右键事件
    • @row-contextmenu:数据行右键事件
    • @cell-dblclick:单元格双击事件
<!-- 表格 -->
<el-table
  :data="testDatas"
  @cell-dblclick="cellDblclick"
  @header-contextmenu="(column, $event) => rightClick(null, column, $event)"
  @row-contextmenu="rightClick"
  :row-class-name="tableRowClassName"
>
  <el-table-column v-if="columnList.length > 0" type="index" :label="'编号'" :width="50"></el-table-column>
  <el-table-column v-for="(column, idx) in columnList" :key="idx" :index="idx">
    <!-- ... -->
  </el-table-column>
</el-table>

<!-- 右键菜单框 -->
<div v-show="showMenu" id="contextmenu" @mouseleave="showMenu = false">
  <i class="el-icon-circle-close hideContextMenu" @click="showMenu = false"></i>
  <el-button size="mini" type="primary">功能</el-button>
</div>

<!-- 单元格/表头内容编辑框 -->
<div v-show="showEditInput" id="editInput" @mouseleave="showEditInput = false">
  <el-input v-model="'单元格/表头内容'">
    <template #prepend>表项名称</template>
  </el-input>
</div>
  • cellDblclick():单元格双击事件 - 更改单元格数值

-> 双击显示输入框(#editInput
-> 找到双击的单元格数据
-> 输入框赋初值
-> 失焦或enter后将输入框的值赋给选中的单元格数据

  • rightClick():单元格/表头右击事件 - 打开菜单

-> 右击显示菜单(#contextmenu
-> 根据表头、表格显示对应菜单
-> 点击调用对应的功能方法

  • tableRowClassName():添加表格行下标

完整代码

Git链接
在线演示

三、组件复用

如果vue项目中需大量运用到此功能,可以通过生成组件来复用。需要注意父子组件间数据的双向绑定。

要在Vue3和element-plus中实现表格行内的编辑和新增,你可以使用element-plus提供的el-table-plus组件来替代el-table,el-table-plus支持行内编辑和新增功能。具体步骤如下: 1. 安装并引入element-plus组件库。 2. 在el-table-plus的column中设置prop属性,用来指定表格列的字段名。 3. 在el-table-plus的column中设置editable属性为true,表示该列可编辑。 4. 在el-table-plus的column中设置edit-template slot来自定义编辑时的表单组件。 5. 使用el-table-plus的@save事件监听保存数据事件,当保存数据时,将数据提交到后台或者更新本地数据。 以下是一个示例代码: ```html <template> <el-table-plus :data="tableData" @save="handleSave"> <el-table-column label="姓名" prop="name" editable></el-table-column> <el-table-column label="年龄" prop="age" editable> <template #edit-template="{ modelValue, row, column }"> <el-input-number v-model="modelValue" :min="0" :max="200" /> </template> </el-table-column> <el-table-column label="地址" prop="address" editable></el-table-column> <el-table-column label="操作" width="120px"> <template #default="{ row, column }"> <el-button @click="handleEdit(row)">编辑</el-button> <el-button @click="handleDelete(row)">删除</el-button> </template> <template #edit="{ row, column }"> <el-button @click="handleSaveEdit(row)">保存</el-button> <el-button @click="handleCancelEdit(row)">取消</el-button> </template> </el-table-column> </el-table-plus> </template> <script> import { ref } from 'vue'; import { ElTablePlus } from 'element-plus'; export default { components: { ElTablePlus, }, setup() { const tableData = ref([ { name: '张三', age: 18, address: '北京市' }, { name: '李四', age: 20, address: '上海市' }, { name: '王五', age: 22, address: '广州市' }, ]); const editData = ref({}); const editIndex = ref(-1); const handleEdit = (row) => { editData.value = Object.assign({}, row); editIndex.value = tableData.value.indexOf(row); }; const handleSaveEdit = (row) => { Object.assign(tableData.value[editIndex.value], editData.value); editIndex.value = -1; editData.value = {}; }; const handleCancelEdit = () => { editIndex.value = -1; editData.value = {}; }; const handleDelete = (row) => { tableData.value.splice(tableData.value.indexOf(row), 1); }; const handleSave = () => { // 提交表格数据到后台或者更新本地数据 }; return { tableData, editData, editIndex, handleEdit, handleSaveEdit, handleCancelEdit, handleDelete, handleSave, }; }, }; </script> ``` 在上面的代码中,我们使用el-table-plus组件替代el-table,并在el-table-plus的column中设置editable属性为true,表示该列可编辑。在edit-template slot中,我们使用el-input-number组件来自定义编辑时的表单组件。在@save事件中,我们监听保存数据事件,并将数据提交到后台或者更新本地数据。在操作列中,我们使用default slot来显示编辑和删除按钮,使用edit slot来显示保存和取消按钮。在handleEdit方法中,我们保存要编辑的数据到editData中,并将要编辑的行的索引保存到editIndex中。在handleSaveEdit方法中,我们将编辑的数据保存到表格数据中,并清空editIndex和editData。在handleCancelEdit方法中,我们清空editIndex和editData。这样就实现表格行内的编辑和新增功能。
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值