Vue + ElementUI 实现表格编辑

2022/06/21 Vue 共 4406 字,约 13 分钟

预览

暂无图片

实现

Table.vue组件部分代码

<template>
  <el-table
    ...
    @cell-click="handleCellClick"
    @cell-dblclick="handleCellDblClick
  >
  </el-table>
</template>

<script>
import { cellOprate } from './extends/index.js'
export default {
  props: {
    columns: {}
  },
  data() {
    return {
      clickTimer: null
    }
  },
  methods: {
    handleCellClick(row, column, cell) {
      clearTimeout(this.clickTimer)
      const col = this.getOriginColumn(column) // 获取 props.columns 中对应的 column
      if (!col.markable) return // 通过 markable 字段控制是否可以单击操作
      this.clickTimer = setTimeout(() => {
        // 鼠标单击事件
        this.handleCellOprate('mark', row, column, cell)
      }, 250)
    },
    handleCellDblClick(row, column, cell) {
      clearTimeout(this.clickTimer)
      const col = this.getOriginColumn(column) // 获取 props.columns 中对应的 column
      if (!col.editable) return // 通过 editable 字段控制是否可以双击编辑
      this.handleCellOprate('input', row, column, cell)
    },
    handleCellOprate(type, row, column, cell) {
      const cellNode = cell.children[0]
      const cellValue = row[column.property] // 获取对应数据
      new cellOprate({
        propsData: {
          type,
          cellValue,
          styles: { width: cellNode.style.width }, // 只需要获取宽度
          classList: cellNode.getAttribute('class')
          onInputBlur: (oldVal, newVal) => {
            // 输入框失焦时触发
          }
        }
      })
    }
  }
}
</script>

extends各组件代码

extends/index.js

import Vue from 'vue'
import definedMark from './Mark.vue'
import definedSpan from './Span.vue'
import definedInput from './Input.vue'
import definedOprate from './Oprate.vue'

export const cellMark = Vue.extend(definedMark)
export const cellSpan = Vue.extend(definedSpan)
export const cellInput = Vue.extend(definedInput)
export const cellOprate = Vue.extend(definedOprate)

extends/Oprate.vue

<template>
  <Span
    v-if="type === 'span'"
    :propsData="propsData"
    @changeType="handleChangeType"
  />

  <Input
    v-else-if="type === 'input'"
    :propsData="propsData"
    @blur="handleInputBlur"
  />

  <Mark
    v-else-if="type === 'mark'"
    :propsData="propsData"
    @changeType="handleChangeType"
  />
</template>

<script>
import Span from './Span.vue'
import Mark from './Mark.vue'
import Input from './Input.vue'
export default {
  components: { Span, Mark, Input },
  props: ['type', 'styles', 'classList', 'cellValue', 'onInputBlur'],
  data() {
    return {

    }
  },
  computed: {
    propsData() {
      return {
        styles: this.styles,
        classList: this.classList,
        cellValue: this.cellValue
      }
    }
  },
  methods: {
    // 输入框 blur 回调
    handleInputBlur(oldVal, newVal) {
      if (typeof this.onInputBlur === 'function') this.onInputBlur(oldVal, newVal)
    },
    // 改变当前显示组件
    handleChangeType(value) {
      this.type = value
    }
  }
}
</script>

extends/Span.vue

<template>
  <!-- el-table 中默认使用 div 渲染的内容,这里也使用div -->
  <div
    :style="propsData.styles"
    :class="propsData.classList"
    @click.stop="handleClick" // 防止触发 el-table  cell-click 事件
    @dblclick.stop="handleDblClick" // 防止触发 el-table  cell-dblclick 事件
  >
    
  </div>
</template>

<script>
export default {
  props: {
    propsData: {
      type: Object,
      default: () => {
        return {
          styles: '',
          classList: '',
          cellValue: ''
        }
      }
    }
  },
  data() {
    return {
      clickTimer: null
    }
  },
  methods: {
    handleClick() {
      clearTimeout(this.clickTimer)
      this.clickTimer = setTimeout(() => {
        this.$emit('changeType', 'mark') // 与 cell-click 逻辑对应
      }, 250)
    },
    handleDblClick() {
      clearTimeout(this.clickTimer)
      this.$emit('changeType', 'input') // 与 cell-dblclick 逻辑对应
    }
  }
}
</script>

extends/Input.vue

<template>
  <el-input
    ref="cellInput"
    v-model="value"
    class="table-cell__input"
    @blur="handleBlur"
    @click.stop.native // 防止触发 el-table  cell-click 事件
    @dblclick.stop.native // 防止触发 el-table  cell-dblclick 事件
  />
</template>

<script>
export default {
  props: {
    propsData: {
      type: Object,
      default: () => {
        return {
          cellValue: ''
        }
      }
    }
  },
  data() {
    return {
      value: ''
    }
  },
  created() {
    this.value = this.propsData.cellValue
  },
  mounted() {
    // 默认聚焦
    this.$refs.cellInput.focus()
  },
  methods: {
    handleBlur() {
      this.$emit('blur', this.propsData.cellValue, this.value)
    }
  }
}
</script>

extends/Mark.vue

<template>
  <el-popover
    ref="cellPopover"
    v-model="visible"
    popper-class="table-cell__popover"
    @hide="handlePopoverHide"
    @click.stop.native // 防止触发 el-table  cell-click 事件
    @dblclick.stop.native // 防止触发 el-table  cell-dblclick 事件
  >
    <el-input
      ref="cellMark"
      v-model="mark"
      type="textarea"
      placeholder="输入内容"
      @click.stop.native // 防止触发 el-table  cell-click 事件
      @dblclick.stop.native // 防止触发 el-table  cell-dblclick 事件
    />
    <el-button @click.stop.native="handleclick">确定</el-button>

    <div
      slot="reference"
      :style="propsData.styles"
      :class="propsData.classList"
    >
      
    </div>
  </el-popover>
</template>

<script>
export default {
  props: {
    propsData: {
      type: Object,
      default: () => {
        return {
          styles: '',
          classList: '',
          cellValue: ''
        }
      }
    }
  },
  data() {
    return {
      mark: '',
      visible: true // 默认加载组件时显示气泡
    }
  },
  methods: {
    handleclick() {
      this.$emit('changeType', 'span')
    },
    handlePopoverHide() {
      this.$emit('changeType', 'span')
    }
  }
}
</script>