乐趣区

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

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

实现功能:

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

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>

退出移动版