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>

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

查看所有标签

猜你喜欢:

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

Blockchain Basics

Blockchain Basics

Daniel Drescher / Apress / 2017-3-16 / USD 20.99

In 25 concise steps, you will learn the basics of blockchain technology. No mathematical formulas, program code, or computer science jargon are used. No previous knowledge in computer science, mathema......一起来看看 《Blockchain Basics》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

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

HEX CMYK 互转工具