ElementUI 实现表格可编辑 Editable,增删改查编辑表格Grid

栏目: JavaScript · 发布时间: 5年前

内容简介:查看实现思路:使用 solt 处理编辑和显示切换已经自定义组件渲染,100%兼容 ElTable 所有参数。Vue + ElementUI 扩展的可编辑表格组件,完全支持任意组件渲染。

查看 Github

实现思路:使用 solt 处理编辑和显示切换已经自定义组件渲染,100%兼容 ElTable 所有参数。

Vue + ElementUI 扩展的可编辑表格组件,完全支持任意组件渲染。

  • 实现功能:

    • 支持单列编辑
    • 支持整行编辑
    • 支持单击、双击编辑模式
    • 支持渲染任意组件
    • 支持动态列
    • 支持显示编辑状态
    • 支持增删改查数据获取
    • 支持还原更改数据
    • 支持 ElTable 所有功能

ElementUI 实现表格可编辑 Editable,增删改查编辑表格Grid

ElementUI 实现表格可编辑 Editable,增删改查编辑表格Grid

ElementUI 实现表格可编辑 Editable,增删改查编辑表格Grid

API

Editable Attributes

<el-editable ref="editable" edit-config="{trigger: 'click', mode: 'cell'}">
  <el-editable-column prop="name" label="名字" edit-render="{name: 'ElInput'}"></el-editable-column>
  <el-editable-column prop="age" label="年龄" edit-render="{name: 'ElInput'}"></el-editable-column>
</el-editable>
属性 描述 类型 可选值 默认值
trigger 触发方式 String manual(手动方式) / click(点击触发编辑) / dblclick(双击触发编辑) click
mode 编辑方式 String cell(列编辑模式) / row(行编辑模式) cell
showIcon 是否显示列头编辑图标 Boolean true
showStatus 是否显示列的编辑状态 Boolean true

Editable Methods

方法名 描述 参数
reload(datas) 初始化加载数据 datas
revert() 还原修改之前数据
insert(record) 新增一行新数据 record
insertAt(record, rowIndex) 指定位置新增一行新数据,如果是-1则从底部新增新数据 record, rowIndex
remove(record) 根据数据删除一行数据 record
removes(records) 根据数据删除多行数据 records
removeRow(rowIndex) 根据行号删除一行数据 rowIndex
removeRows(rowIndexs) 根据行号删除多行数据 rowIndexs
removeSelecteds() 删除选中行数据
clear() 清空所有数据
clearActive() 清除所有活动行列为不可编辑状态
setActiveRow(rowIndex) 设置活动行为可编辑状态(只对mode='row'有效) rowIndex
updateStatus(scope) 更新列状态(当使用自定义组件时,值发生改变时需要调用来更新列状态),如果不传参数则更新整个表格 scope
getRecords() 获取表格数据集
getAllRecords() 获取表格所有数据
getInsertRecords() 获取新增数据
getRemoveRecords() 获取已删除数据
getUpdateRecords() 获取已修改数据

Editable-Column Attributes

<el-editable-column prop="name" label="名字" edit-render="{name: 'ElInput', type: 'default'}"></el-editable-column>
属性 描述 类型 可选值 默认值
name 渲染的组件名称 String ElInput / ElSelect / ElCascader / ElDatePicker / ElInputNumber / ElSwitch ElInput
type 渲染类型 String default(组件激活后才可视) / visible(组件一直可视) default
attrs 渲染组件附加属性 Object {}
optionAttrs 下拉组件选项附加属性(只对name='ElSelect'有效) Object {}
options 下拉组件选项列表(只对name='ElSelect'有效) Array []

Editable-Column Scoped Slot

name 说明
自定义渲染显示内容,参数为 { row, column, $index }
type 自定义渲染组件,参数为 { row, column, $index }
head 自定义表头的内容,参数为 { column, $index }

Editable.vue

<template>
  <el-table
    ref="refElTable"
    :class="['editable', {'editable--icon': showIcon}]"
    :data="datas"
    :height="height"
    :maxHeight="maxHeight"
    :stripe="stripe"
    :border="border"
    :size="size"
    :fit="fit"
    :showHeader="showHeader"
    :highlightCurrentRow="highlightCurrentRow"
    :currentRowKey="currentRowKey"
    :rowClassName="rowClassName"
    :rowStyle="rowStyle"
    :headerRowClassName="headerRowClassName"
    :headerRowStyle="headerRowStyle"
    :headerCellClassName="headerCellClassName"
    :headerCellStyle="headerCellStyle"
    :rowKey="rowKey"
    :emptyText="emptyText"
    :defaultExpandAll="defaultExpandAll"
    :expandRowKeys="expandRowKeys"
    :defaultSort="defaultSort"
    :tooltipEffect="tooltipEffect"
    :showSummary="showSummary"
    :sumText="sumText"
    :summaryMethod="_summaryMethod"
    :selectOnIndeterminate="selectOnIndeterminate"
    :spanMethod="_spanMethod"
    @select="_select"
    @select-all="_selectAll"
    @selection-change="_selectionChange"
    @cell-mouse-enter="_cellMouseEnter"
    @cell-mouse-leave="_cellMouseLeave"
    @cell-click="_cellClick"
    @cell-dblclick="_cellDBLclick"
    @row-click="_rowClick"
    @row-contextmenu="_rowContextmenu"
    @row-dblclick="_rowDBLclick"
    @header-click="_headerClick"
    @header-contextmenu="_headerContextmenu"
    @sort-change="_sortChange"
    @filter-change="_filterChange"
    @current-change="_currentChange"
    @header-dragend="_headerDragend"
    @expand-change="_expandChange">
    <slot></slot>
  </el-table>
</template>

<script>
import XEUtils from 'xe-utils'
import { mapGetters } from 'vuex'

export default {
  name: 'ElEditable',
  props: {
    editConfig: Object,

    data: Array,
    height: [String, Number],
    maxHeight: [String, Number],
    stripe: Boolean,
    border: Boolean,
    size: { type: String, default: 'small' },
    fit: { type: Boolean, default: true },
    showHeader: { type: Boolean, default: true },
    highlightCurrentRow: Boolean,
    currentRowKey: [String, Number],
    rowClassName: [Function, String],
    rowStyle: [Function, Object],
    cellClassName: [Function, String],
    cellStyle: [Function, Object],
    headerRowClassName: [Function, String],
    headerRowStyle: [Function, Object],
    headerCellClassName: [Function, String],
    headerCellStyle: [Function, Object],
    rowKey: [Function, String],
    emptyText: String,
    defaultExpandAll: Boolean,
    expandRowKeys: Array,
    defaultSort: Object,
    tooltipEffect: { type: String, default: 'dark' },
    showSummary: Boolean,
    sumText: { type: String, default: '合计' },
    summaryMethod: Function,
    selectOnIndeterminate: { type: Boolean, default: true },
    spanMethod: Function
  },
  data () {
    return {
      editProto: {},
      datas: [],
      initialStore: [],
      deleteRecords: [],
      lastActive: null
    }
  },
  computed: {
    ...mapGetters([
      'globalClick'
    ]),
    showIcon () {
      return this.editConfig ? !(this.editConfig.showIcon === false) : true
    },
    showStatus () {
      return this.editConfig ? !(this.editConfig.showStatus === false) : true
    },
    mode () {
      return this.editConfig ? (this.editConfig.mode || 'cell') : 'cell'
    }
  },
  watch: {
    globalClick (evnt) {
      if (this.lastActive) {
        let target = evnt.target
        let { cell } = this.lastActive
        while (target && target.nodeType && target !== document) {
          if (this.mode === 'row' ? target === cell.parentNode : target === cell) {
            return
          }
          target = target.parentNode
        }
        this.clearActive()
      }
    },
    datas () {
      this.updateStatus()
    }
  },
  created () {
    this._initial(this.data, true)
  },
  methods: {
    _initial (datas, isReload) {
      if (isReload) {
        this.initialStore = XEUtils.clone(datas, true)
      }
      this.datas = (datas || []).map(item => this._toData(item))
    },
    _toData (item, status) {
      return item.editable && item._EDITABLE_PROTO === this.editProto ? item : {
        _EDITABLE_PROTO: this.editProto,
        data: item,
        store: XEUtils.clone(item, true),
        editStatus: status || 'initial',
        editActive: null,
        editable: {
          size: this.size,
          showIcon: this.showIcon,
          showStatus: this.showStatus,
          mode: this.mode
        }
      }
    },
    _updateData () {
      this.$emit('update:data', this.datas.map(item => item.data))
    },
    _select (selection, row) {
      this.$emit('select', selection.map(item => item.data), row.data)
    },
    _selectAll (selection) {
      this.$emit('select-all', selection.map(item => item.data))
    },
    _selectionChange (selection) {
      this.$emit('selection-change', selection.map(item => item.data))
    },
    _cellMouseEnter (row, column, cell, event) {
      this.$emit('cell-mouse-enter', row.data, column, cell, event)
    },
    _cellMouseLeave (row, column, cell, event) {
      this.$emit('cell-mouse-leave', row.data, column, cell, event)
    },
    _cellClick (row, column, cell, event) {
      if (!this.editConfig || this.editConfig.trigger === 'click') {
        this._triggerActive(row, column, cell)
      }
      this.$emit('cell-click', row.data, column, cell, event)
    },
    _cellDBLclick (row, column, cell, event) {
      if (this.editConfig && this.editConfig.trigger === 'dblclick') {
        this._triggerActive(row, column, cell)
      }
      this.$emit('cell-dblclick', row.data, column, cell, event)
    },
    _rowClick (row, event, column) {
      this.$emit('row-click', row.data, event, column)
    },
    _rowContextmenu (row, event) {
      this.$emit('row-contextmenu', row.data, event)
    },
    _rowDBLclick (row, event) {
      this.$emit('row-dblclick', row.data, event)
    },
    _headerClick (column, event) {
      this.$emit('header-click', column, event)
    },
    _headerContextmenu (column, event) {
      this.$emit('header-contextmenu', column, event)
    },
    _sortChange ({ column, prop, order }) {
      this.$emit('sort-change', { column, prop, order })
    },
    _filterChange (filters) {
      this.$emit('filter-change', filters)
    },
    _currentChange (currentRow, oldCurrentRow) {
      if (currentRow && oldCurrentRow) {
        this.$emit('current-change', currentRow.data, oldCurrentRow.data)
      } else if (currentRow) {
        this.$emit('current-change', currentRow.data)
      } else if (oldCurrentRow) {
        this.$emit('current-change', oldCurrentRow.data)
      }
    },
    _headerDragend (newWidth, oldWidth, column, event) {
      this.$emit('header-dragend', newWidth, oldWidth, column, event)
    },
    _expandChange (row, expandedRows) {
      this.$emit('expand-change', row.data, expandedRows)
    },
    _triggerActive (row, column, cell) {
      this.clearActive()
      this.lastActive = { row, column, cell }
      cell.className += ` editable-col_active`
      row.editActive = column.property
      this.$nextTick(() => {
        let inpElem = cell.querySelector('.el-input>input')
        if (!inpElem) {
          inpElem = cell.querySelector('.el-textarea>textarea')
          if (!inpElem) {
            inpElem = cell.querySelector('.editable-custom_input')
          }
        }
        if (inpElem) {
          inpElem.focus()
        }
      })
    },
    _updateColumnStatus (trElem, column, tdElem) {
      if (column.className.split(' ').includes('editable-col_edit')) {
        let classList = tdElem.className.split(' ')
        if (!classList.includes('editable-col_dirty')) {
          classList.push('editable-col_dirty')
          tdElem.className = classList.join(' ')
        }
      }
    },
    _summaryMethod (param) {
      let { columns } = param
      let data = param.data.map(item => item.data)
      let sums = []
      if (this.summaryMethod) {
        sums = this.summaryMethod({ columns, data })
      } else {
        columns.forEach((column, index) => {
          if (index === 0) {
            sums[index] = this.sumText
            return
          }
          let values = data.map(item => Number(item[column.property]))
          let precisions = []
          let notNumber = true
          values.forEach(value => {
            if (!isNaN(value)) {
              notNumber = false
              let decimal = ('' + value).split('.')[1]
              precisions.push(decimal ? decimal.length : 0)
            }
          })
          let precision = Math.max.apply(null, precisions)
          if (!notNumber) {
            sums[index] = values.reduce((prev, curr) => {
              let value = Number(curr)
              if (!isNaN(value)) {
                return parseFloat((prev + curr).toFixed(Math.min(precision, 20)))
              } else {
                return prev
              }
            }, 0)
          } else {
            sums[index] = ''
          }
        })
      }
      return sums
    },
    _spanMethod ({ row, column, rowIndex, columnIndex }) {
      let rowspan = 1
      let colspan = 1
      let fn = this.spanMethod
      if (XEUtils.isFunction(fn)) {
        var result = fn({
          row: row.data,
          column: column,
          rowIndex: rowIndex,
          columnIndex: columnIndex
        })
        if (XEUtils.isArray(result)) {
          rowspan = result[0]
          colspan = result[1]
        } else if (XEUtils.isPlainObject(result)) {
          rowspan = result.rowspan
          colspan = result.colspan
        }
      }
      return {
        rowspan: rowspan,
        colspan: colspan
      }
    },
    clearActive () {
      this.lastActive = null
      this.datas.forEach(item => {
        item.editActive = null
      })
      Array.from(this.$el.querySelectorAll('.editable-col_active.editable-column')).forEach(elem => {
        elem.className = elem.className.split(' ').filter(name => name !== 'editable-col_active').join(' ')
      })
    },
    setActiveRow (rowIndex) {
      setTimeout(() => {
        let row = this.datas[rowIndex]
        if (row && this.mode === 'row') {
          let column = this.$refs.refElTable.columns.find(column => column.property)
          let trElemList = this.$el.querySelectorAll('.el-table__body-wrapper .el-table__row')
          let cell = trElemList[rowIndex].children[0]
          this._triggerActive(row, column, cell)
        }
      }, 5)
    },
    reload (datas) {
      this.deleteRecords = []
      this.clearActive()
      this._initial(datas, true)
      this._updateData()
    },
    revert () {
      this.reload(this.initialStore)
    },
    clear () {
      this.deleteRecords = []
      this.clearActive()
      this._initial([])
      this._updateData()
    },
    insert (record) {
      this.insertAt(record, 0)
    },
    insertAt (record, rowIndex) {
      let recordItem = {}
      let len = this.datas.length
      this.$refs.refElTable.columns.forEach(column => {
        if (column.property) {
          recordItem[column.property] = null
        }
      })
      recordItem = this._toData(Object.assign(recordItem, record), 'insert')
      if (rowIndex) {
        if (rowIndex === -1 || rowIndex >= len) {
          rowIndex = len
          this.datas.push(recordItem)
        } else {
          this.datas.splice(rowIndex, 0, recordItem)
        }
      } else {
        rowIndex = 0
        this.datas.unshift(recordItem)
      }
      this._updateData()
    },
    removeRow (rowIndex) {
      let items = this.datas.splice(rowIndex, 1)
      items.forEach(item => {
        if (item.editStatus === 'initial') {
          this.deleteRecords.push(item)
        }
      })
      this._updateData()
    },
    removeRows (rowIndexs) {
      XEUtils.lastEach(this.datas, (item, index) => {
        if (rowIndexs.includes(index)) {
          this.removeRow(index)
        }
      })
    },
    remove (record) {
      this.removeRow(XEUtils.findIndexOf(this.datas, item => item.data === record))
    },
    removes (records) {
      XEUtils.lastEach(this.datas, (item, index) => {
        if (records.includes(item.data)) {
          this.removeRow(index)
        }
      })
    },
    removeSelecteds () {
      this.removes(this.$refs.refElTable.selection.map(item => item.data))
    },
    getRecords (datas) {
      return (datas || this.datas).map(item => item.data)
    },
    getAllRecords () {
      return {
        records: this.getRecords(),
        insertRecords: this.getInsertRecords(),
        removeRecords: this.getRemoveRecords(),
        updateRecords: this.getUpdateRecords()
      }
    },
    getInsertRecords () {
      return this.getRecords(this.datas.filter(item => item.editStatus === 'insert'))
    },
    getRemoveRecords () {
      return this.getRecords(this.deleteRecords)
    },
    getUpdateRecords () {
      return this.getRecords(this.datas.filter(item => item.editStatus === 'initial' && !XEUtils.isEqual(item.data, item.store)))
    },
    updateStatus (scope) {
      if (this.showStatus) {
        if (arguments.length === 0) {
          this.$nextTick(() => {
            let trElems = this.$el.querySelectorAll('.el-table__row')
            if (trElems.length) {
              let columns = this.$refs.refElTable.columns
              this.datas.forEach((item, index) => {
                let trElem = trElems[index]
                if (trElem.children.length) {
                  if (item.editStatus === 'insert') {
                    columns.forEach((column, cIndex) => this._updateColumnStatus(trElem, column, trElem.children[cIndex]))
                  } else {
                    columns.forEach((column, cIndex) => {
                      let tdElem = trElem.children[cIndex]
                      if (tdElem) {
                        if (XEUtils.isEqual(item.data[column.property], item.store[column.property])) {
                          let classList = tdElem.className.split(' ')
                          tdElem.className = classList.filter(name => name !== 'editable-col_dirty').join(' ')
                        } else {
                          this._updateColumnStatus(trElem, column, trElem.children[cIndex])
                        }
                      }
                    })
                  }
                }
              })
            }
          })
        } else {
          this.$nextTick(() => {
            let { $index, _row, column, store } = scope
            let trElems = store.table.$el.querySelectorAll('.el-table__row')
            if (trElems.length) {
              let trElem = trElems[$index]
              let tdElem = trElem.querySelector(`.${column.id}`)
              if (tdElem) {
                let classList = tdElem.className.split(' ')
                if (XEUtils.isEqual(_row.data[column.property], _row.store[column.property])) {
                  tdElem.className = classList.filter(name => name !== 'editable-col_dirty').join(' ')
                } else {
                  this._updateColumnStatus(trElem, column, tdElem)
                }
              }
            }
          })
        }
      }
    }
  }
}
</script>

EditableColumn.vue

<template>
  <el-table-column v-if="type === 'selection' || group" v-bind="attrs">
    <slot></slot>
  </el-table-column>
  <el-table-column v-else-if="type === 'index'" v-bind="attrs">
    <template slot="header" slot-scope="scope">
      <slot name="head" v-bind="{$index: scope.$index, column: scope.column, store: scope.store}">#</slot>
    </template>
    <slot></slot>
  </el-table-column>
  <el-table-column v-else-if="type === 'expand'" v-bind="attrs">
    <template slot="header" slot-scope="scope">
      <slot name="head" v-bind="{$index: scope.$index, column: scope.column, store: scope.store}"></slot>
    </template>
    <template slot-scope="scope">
      <slot v-bind="{$index: scope.$index, row: scope.row.data, column: scope.column, store: scope.store, _row: scope.row}"></slot>
    </template>
  </el-table-column>
  <el-table-column v-else-if="editRender" v-bind="attrs">
    <template slot="header" slot-scope="scope">
      <slot name="head" v-bind="{$index: scope.$index, column: scope.column, store: scope.store}">
        <i class="el-icon-edit-outline editable-header-icon"></i>{{ scope.column.label }}
      </slot>
    </template>
    <template slot-scope="scope">
      <template v-if="editRender.type === 'visible'">
        <slot name="edit" v-bind="{$index: scope.$index, row: scope.row.data, column: scope.column, store: scope.store, _row: scope.row}">
          <template v-if="editRender.name === 'ElSelect'">
            <el-select v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)">
              <el-option v-for="(item, index) in editRender.options" :key="index" :value="item.value" :label="item.label" v-bind="editRender.optionAttrs"></el-option>
            </el-select>
          </template>
          <template v-else-if="editRender.name === 'ElCascader'">
            <el-cascader v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-cascader>
          </template>
          <template v-else-if="editRender.name === 'ElTimePicker'">
            <el-time-picker v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-time-picker>
          </template>
          <template v-else-if="editRender.name === 'ElDatePicker'">
            <el-date-picker v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-date-picker>
          </template>
          <template v-else-if="editRender.name === 'ElInputNumber'">
            <el-input-number v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-input-number>
          </template>
          <template v-else-if="editRender.name === 'ElSwitch'">
            <el-switch v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-switch>
          </template>
          <template v-else-if="editRender.name === 'ElRate'">
            <el-rate v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-rate>
          </template>
          <template v-else-if="editRender.name === 'ElColorPicker'">
            <el-color-picker v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-color-picker>
          </template>
          <template v-else-if="editRender.name === 'ElSlider'">
            <el-slider v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-slider>
          </template>
          <template v-else>
            <el-input v-model="scope.row.data[scope.column.property]" @change="changeEvent(scope)"></el-input>
          </template>
        </slot>
      </template>
      <template v-else>
        <template v-if="scope.row.editable.mode === 'row' ? scope.row.editActive : scope.row.editActive === scope.column.property">
          <slot name="edit" v-bind="{$index: scope.$index, row: scope.row.data, column: scope.column, store: scope.store, _row: scope.row}">
            <template v-if="editRender.name === 'ElSelect'">
              <el-select v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)">
                <el-option v-for="(item, index) in editRender.options" :key="index" :value="item.value" :label="item.label" v-bind="editRender.optionAttrs"></el-option>
              </el-select>
            </template>
            <template v-else-if="editRender.name === 'ElCascader'">
              <el-cascader v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-cascader>
            </template>
            <template v-else-if="editRender.name === 'ElTimePicker'">
              <el-time-picker v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-time-picker>
            </template>
            <template v-else-if="editRender.name === 'ElDatePicker'">
              <el-date-picker v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-date-picker>
            </template>
            <template v-else-if="editRender.name === 'ElInputNumber'">
              <el-input-number v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-input-number>
            </template>
            <template v-else-if="editRender.name === 'ElSwitch'">
              <el-switch v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-switch>
            </template>
            <template v-else-if="editRender.name === 'ElRate'">
              <el-rate v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-rate>
            </template>
            <template v-else-if="editRender.name === 'ElColorPicker'">
              <el-color-picker v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-color-picker>
            </template>
            <template v-else-if="editRender.name === 'ElSlider'">
              <el-slider v-model="scope.row.data[scope.column.property]" v-bind="getRendAttrs(scope)" @change="changeEvent(scope)"></el-slider>
            </template>
            <template v-else>
              <el-input v-model="scope.row.data[scope.column.property]" @change="changeEvent(scope)"></el-input>
            </template>
          </slot>
        </template>
        <template v-else>
          <slot v-bind="{$index: scope.$index, row: scope.row.data, column: scope.column, store: scope.store, _row: scope.row}">
            <template v-if="editRender.name === 'ElSelect'">{{ getSelectLabel(scope) }}</template>
            <template v-else-if="editRender.name === 'ElCascader'">{{ getCascaderLabel(scope) }}</template>
            <template v-else-if="editRender.name === 'ElTimePicker'">{{ getTimePickerLabel(scope) }}</template>
            <template v-else-if="editRender.name === 'ElDatePicker'">{{ getDatePickerLabel(scope) }}</template>
            <template v-else>{{ formatter ? formatter(scope.row.data, scope.column, scope.row.data[scope.column.property], scope.$index) : scope.row.data[scope.column.property] }}</template>
          </slot>
        </template>
      </template>
    </template>
  </el-table-column>
  <el-table-column v-else v-bind="attrs">
    <template slot-scope="scope">
      <slot v-bind="{$index: scope.$index, row: scope.row.data, column: scope.column, store: scope.store, _row: scope.row}">{{ formatter ? formatter(scope.row.data, scope.column, scope.row.data[scope.column.property], scope.$index) : scope.row.data[scope.column.property] }}</slot>
    </template>
  </el-table-column>
</template>

<script>
import XEUtils from 'xe-utils'

export default {
  name: 'ElEditableColumn',
  props: {
    group: Boolean,
    editRender: Object,

    index: [Number, Function],
    type: String,
    label: String,
    columnKey: String,
    prop: String,
    width: String,
    minWidth: String,
    fixed: [Boolean, String],
    sortable: [Boolean, String],
    sortMethod: Function,
    sortBy: [String, Array, Function],
    sortOrders: Array,
    resizable: { type: Boolean, default: true },
    formatter: Function,
    showOverflowTooltip: Boolean,
    align: { type: String, default: 'left' },
    headerAlign: String,
    className: { type: String, default: '' },
    labelClassName: String,
    selectable: Function,
    reserveSelection: Boolean,
    filters: Array,
    filterPlacement: String,
    filterMultiple: { type: Boolean, default: true },
    filterMethod: Function,
    filteredValue: Array
  },
  computed: {
    attrs () {
      return {
        index: this.index,
        type: this.type,
        label: this.label,
        columnKey: this.columnKey,
        prop: this.prop,
        width: this.width,
        minWidth: this.minWidth,
        fixed: this.fixed,
        sortable: this.sortable,
        sortMethod: this.sortMethod ? this.sortMethodEvent : this.sortMethod,
        sortBy: XEUtils.isFunction(this.sortBy) ? this.sortByEvent : this.sortBy,
        sortOrders: this.sortOrders,
        resizable: this.resizable,
        showOverflowTooltip: this.showOverflowTooltip,
        align: this.align,
        headerAlign: this.headerAlign,
        className: `editable-column ${this.editRender ? 'editable-col_edit' : 'editable-col_readonly'}${this.className ? ' ' + this.className : ''}`,
        labelClassName: this.labelClassName,
        selectable: this.selectable ? this.selectableEvent : this.selectable,
        reserveSelection: this.reserveSelection,
        filters: this.filters,
        filterPlacement: this.filterPlacement,
        filterMultiple: this.filterMultiple,
        filterMethod: this.filterMethod ? this.filterMethodEvent : this.filterMethod,
        filteredValue: this.filteredValue
      }
    }
  },
  methods: {
    getRendAttrs ({ row }) {
      let size = row.editable.size
      return Object.assign({ size }, this.editRender.attrs)
    },
    getSelectLabel (scope) {
      let value = scope.row.data[scope.column.property]
      let selectItem = this.editRender.options.find(item => item.value === value)
      return selectItem ? selectItem.label : null
    },
    matchCascaderData (values, index, list, labels) {
      let val = values[index]
      if (list && values.length > index) {
        list.forEach(item => {
          if (item.value === val) {
            labels.push(item.label)
            this.matchCascaderData(values, ++index, item.children, labels)
          }
        })
      }
    },
    getCascaderLabel (scope) {
      let values = scope.row.data[scope.column.property] || []
      let labels = []
      let attrs = this.editRender.attrs || {}
      this.matchCascaderData(values, 0, attrs.options || [], labels)
      return labels.join(attrs.separator || '/')
    },
    getTimePickerLabel (scope) {
      let value = scope.row.data[scope.column.property]
      let attrs = this.editRender.attrs || {}
      return XEUtils.toDateString(value, attrs.format || 'hh:mm:ss')
    },
    getDatePickerLabel (scope) {
      let value = scope.row.data[scope.column.property]
      let attrs = this.editRender.attrs || {}
      if (attrs.type === 'datetimerange') {
        return XEUtils.toArray(value).map(date => XEUtils.toDateString(date, attrs.format)).join(attrs.rangeSeparator)
      }
      return XEUtils.toDateString(value, attrs.format, 'yyyy-MM-dd')
    },
    sortByEvent (row, index) {
      return this.sortBy(row.data, index)
    },
    sortMethodEvent (a, b) {
      return this.sortMethod(a.data, b.data)
    },
    selectableEvent (row, index) {
      return this.selectable(row.data, index)
    },
    filterMethodEvent (value, row, column) {
      return this.filterMethod(value, row.data, column)
    },
    changeEvent ({ $index, row, column, store }) {
      if (row.editable.showStatus) {
        this.$nextTick(() => {
          let trElem = store.table.$el.querySelectorAll('.el-table__row')[$index]
          let tdElem = trElem.querySelector(`.${column.id}`)
          let classList = tdElem.className.split(' ')
          if (XEUtils.isEqual(row.data[column.property], row.store[column.property])) {
            tdElem.className = classList.filter(name => name !== 'editable-col_dirty').join(' ')
          } else {
            if (!classList.includes('editable-col_dirty')) {
              classList.push('editable-col_dirty')
              tdElem.className = classList.join(' ')
            }
          }
        })
      }
    }
  }
}
</script>

<style lang="scss">
.editable {
  &.editable--icon {
    .editable-header-icon {
      display: inline-block;
    }
  }
  &.el-table--mini {
    .editable-column {
      height: 42px;
    }
  }
  &.el-table--small {
    .editable-column {
      height: 48px;
    }
  }
  &.el-table--medium {
    .editable-column {
      height: 62px;
    }
  }
  .editable-header-icon {
    display: none;
  }
  .editable-column {
    height: 62px;
    padding: 0;
    &.editable-col_dirty {
      position: relative;
      &:before {
        content: '';
        top: -5px;
        left: -5px;
        position: absolute;
        border: 5px solid;
        border-color: transparent #C00000 transparent transparent;
        transform: rotate(45deg);
      }
    }
    .cell {
      >.edit-input,
      >.el-cascader,
      >.el-autocomplete,
      >.el-input-number,
      >.el-date-editor,
      >.el-select {
        width: 100%;
      }
    }
  }
}
</style>

使用

  • 全局事件需要依赖 vuex 中的 globalClick 变量 (参考store/modules/event.js)
  • 将 Editable.vue 和 EditableColumn.vue 复制到自己项目的 components 目录下
  • 然后在 main.js 引入组件即可
import Editable from '@/components/Editable.vue'
import EditableColumn from '@/components/EditableColumn.vue'

Vue.component(Editable.name, Editable)
Vue.component(EditableColumn.name, EditableColumn)
<template>
  <div>
    <el-button type="primary" @click="$refs.editable.insert({name: 'new1'})">新增</el-button>
    <el-button type="danger" @click="$refs.editable.removeSelecteds()">删除选中</el-button>
    <el-button type="danger" @click="$refs.editable.clear()">清空所有</el-button>
    <el-editable ref="editable" :data.sync="list">
      <el-editable-column type="selection" width="55"></el-editable-column>
      <el-editable-column type="index" width="55"></el-editable-column>
      <el-editable-column prop="name" label="名字"></el-editable-column>
      <el-editable-column prop="sex" label="性别" :editRender="{name: 'ElSelect', options: sexList}"></el-editable-column>
      <el-editable-column prop="age" label="年龄" :editRender="{name: 'ElInputNumber', attrs: {min: 1, max: 200}}"></el-editable-column>
      <el-editable-column prop="region" label="地区" :editRender="{name: 'ElCascader', attrs: {options: regionList}}"></el-editable-column>
      <el-editable-column prop="birthdate" label="出生日期" :editRender="{name: 'ElDatePicker', attrs: {type: 'date', format: 'yyyy-MM-dd'}}"></el-editable-column>
      <el-editable-column prop="date1" label="选择日期" :editRender="{name: 'ElDatePicker', attrs: {type: 'datetime', format: 'yyyy-MM-dd hh:mm:ss'}}"></el-editable-column>
      <el-editable-column prop="flag" label="是否启用" :editRender="{name: 'ElSwitch'}"></el-editable-column>
      <el-editable-column prop="remark" label="备注" :editRender="{name: 'ElInput'}"></el-editable-column>
      <el-editable-column label="操作">
        <template slot-scope="scope">
          <el-button size="mini" type="danger" @click="removeEvent(scope.row, scope.$index)">删除</el-button>
        </template>
      </el-editable-column>
    </el-editable>
  </div>
</template>

<script>
import { MessageBox } from 'element-ui'

export default {
  data () {
    return {
      sexList: [
        {
          label: '男',
          value: '1'
        },
        {
          label: '女',
          value: '0'
        }
      ],
      regionList: [
        {
          value: 'bj',
          label: '北京',
          children: [
            {
              value: 'bjs',
              label: '北京市',
              children: [
                {
                  value: 'dcq',
                  label: '东城区'
                }
              ]
            }
          ]
        },
        {
          value: 'gds',
          label: '广东省',
          children: [
            {
              value: 'szs',
              label: '深圳市',
              children: [
                {
                  value: 'lhq',
                  label: '罗湖区'
                }
              ]
            },
            {
              value: 'gzs',
              label: '广州市',
              children: [
                {
                  value: 'thq',
                  label: '天河区'
                }
              ]
            }
          ]
        }
      ],
      list: [
        {
          name: 'test11',
          height: 176,
          age: 26,
          sex: '1',
          region: null,
          birthdate: new Date(1994, 0, 1),
          date1: new Date(2019, 0, 1, 20, 0, 30),
          remark: '备注1',
          flag: false
        },
        {
          name: 'test22',
          height: 166,
          age: 24,
          sex: '0',
          region: ['gds', 'szs', 'lhq'],
          birthdate: new Date(1992, 0, 1),
          date1: new Date(2019, 0, 1, 12, 10, 30),
          remark: '备注2',
          flag: true
        },
        {
          name: 'test33',
          height: 172,
          age: 22,
          sex: '1',
          region: ['bj', 'bjs', 'dcq'],
          birthdate: new Date(1990, 0, 1),
          date1: new Date(2019, 0, 1, 0, 30, 50),
          remark: null,
          flag: false
        }
      ]
    }
  },
  methods: {
    removeEvent (row, index) {
      MessageBox.confirm('确定删除该数据?', '温馨提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.$refs.editable.removeRow(index)
      }).catch(e => e)
    }
  }
}
</script>

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员修炼之道

程序员修炼之道

Andrew Hunt、David Thomas / 马维达 / 电子工业出版社 / 2011-1 / 55.00元

《程序员修炼之道:从小工到专家》内容简介:《程序员修炼之道》由一系列独立的部分组成,涵盖的主题从个人责任、职业发展,知道用于使代码保持灵活、并且易于改编和复用的各种架构技术,利用许多富有娱乐性的奇闻轶事、有思想性的例子及有趣的类比,全面阐释了软件开发的许多不同方面的最佳实践和重大陷阱。无论你是初学者,是有经验的程序员,还是软件项目经理,《程序员修炼之道:从小工到专家》都适合你阅读。一起来看看 《程序员修炼之道》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具