实现思路:使用 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>
评论列表
发表评论
热评文章
相关阅读