yw-common-list
Version:
基于vxe-table封装的自定义可配置表格
707 lines (704 loc) • 24.6 kB
JavaScript
import XEUtils from 'xe-utils'
import VXETable from '../../v-x-e-table'
import UtilTools, { isEnableConf } from '../../tools/utils'
import { getRowid } from '../../table/src/util'
import DomTools, { browse } from '../../tools/dom'
import { warnLog, errLog, getLog } from '../../tools/log'
function insertTreeRow (_vm, newRecords, isAppend) {
const { tableFullTreeData, afterFullData, fullDataRowIdData, fullAllDataRowIdData, treeOpts } = _vm
const { rowField, parentField, children, mapChildren } = treeOpts
const funcName = isAppend ? 'push' : 'unshift'
newRecords.forEach(item => {
const parentRowId = item[parentField]
const rowid = getRowid(_vm, item)
const matchObj = parentRowId ? XEUtils.findTree(tableFullTreeData, item => parentRowId === item[rowField], { children: mapChildren }) : null
if (matchObj) {
const { item: parentRow } = matchObj
const parentRest = fullAllDataRowIdData[getRowid(_vm, parentRow)]
const parentLevel = parentRest ? parentRest.level : 0
let parentChilds = parentRow[children]
if (!XEUtils.isArray(parentChilds)) {
parentChilds = parentRow[children] = []
}
parentChilds[funcName](item)
const rest = { row: item, rowid, seq: -1, index: -1, _index: -1, $index: -1, items: parentChilds, parent, level: parentLevel + 1 }
fullDataRowIdData[rowid] = rest
fullAllDataRowIdData[rowid] = rest
} else {
if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
if (parentRowId) {
warnLog('vxe.error.unableInsert')
}
}
afterFullData[funcName](item)
tableFullTreeData[funcName](item)
const rest = { row: item, rowid, seq: -1, index: -1, _index: -1, $index: -1, items: tableFullTreeData, parent: null, level: 0 }
fullDataRowIdData[rowid] = rest
fullAllDataRowIdData[rowid] = rest
}
})
}
export default {
methods: {
/**
* 往表格中插入临时数据
*
* @param {*} records
*/
_insert (records) {
return this.insertAt(records)
},
/**
* 往表格指定行中插入临时数据
* 如果 row 为空则从插入到顶部
* 如果 row 为 -1 则从插入到底部
* 如果 row 为有效行则插入到该行的位置
* @param {Object/Array} records 新的数据
* @param {Row} row 指定行
*/
_insertAt (records, row) {
const { tableFullTreeData, mergeList, afterFullData, editStore, tableFullData, treeConfig, fullDataRowIdData, fullAllDataRowIdData, treeOpts } = this
const { transform, rowField, mapChildren } = treeOpts
if (!XEUtils.isArray(records)) {
records = [records]
}
const newRecords = records.map(record => this.defineField(Object.assign({}, record)))
if (!row) {
// 如果为虚拟树
if (treeConfig && transform) {
insertTreeRow(this, newRecords, false)
} else {
afterFullData.unshift(...newRecords)
tableFullData.unshift(...newRecords)
// 刷新单元格合并
mergeList.forEach(mergeItem => {
const { row: mergeRowIndex } = mergeItem
if (mergeRowIndex > 0) {
mergeItem.row = mergeRowIndex + newRecords.length
}
})
}
} else {
if (row === -1) {
// 如果为虚拟树
if (treeConfig && transform) {
insertTreeRow(this, newRecords, true)
} else {
afterFullData.push(...newRecords)
tableFullData.push(...newRecords)
// 刷新单元格合并
mergeList.forEach(mergeItem => {
const { row: mergeRowIndex, rowspan: mergeRowspan } = mergeItem
if (mergeRowIndex + mergeRowspan > afterFullData.length) {
mergeItem.rowspan = mergeRowspan + newRecords.length
}
})
}
} else {
// 如果为虚拟树
if (treeConfig && transform) {
const matchObj = XEUtils.findTree(tableFullTreeData, item => row[rowField] === item[rowField], { children: mapChildren })
if (matchObj) {
const { parent: parentRow } = matchObj
const parentChilds = matchObj.items
const parentRest = fullAllDataRowIdData[getRowid(this, parentRow)]
const parentLevel = parentRest ? parentRest.level : 0
newRecords.forEach((item, i) => {
const rowid = getRowid(this, item)
if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
if (item[treeOpts.parentField]) {
if (parentRow && item[treeOpts.parentField] !== parentRow[rowField]) {
errLog('vxe.error.errProp', [`${treeOpts.parentField}=${item[treeOpts.parentField]}`, `${treeOpts.parentField}=${parentRow[rowField]}`])
}
}
}
if (parentRow) {
item[treeOpts.parentField] = parentRow[rowField]
}
parentChilds.splice(matchObj.index + i, 0, item)
const rest = { row: item, rowid, seq: -1, index: -1, _index: -1, $index: -1, items: parentChilds, parent: parentRow, level: parentLevel + 1 }
fullDataRowIdData[rowid] = rest
fullAllDataRowIdData[rowid] = rest
})
} else {
if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
warnLog('vxe.error.unableInsert')
}
insertTreeRow(this, newRecords, true)
}
} else {
if (treeConfig) {
throw new Error(getLog('vxe.error.noTree', ['insert']))
}
let afIndex = -1
// 如果是可视索引
if (XEUtils.isNumber(row)) {
if (row < afterFullData.length) {
afIndex = row
}
} else {
afIndex = this.findRowIndexOf(afterFullData, row)
}
if (afIndex === -1) {
throw new Error(errLog('vxe.error.unableInsert'))
}
afterFullData.splice(afIndex, 0, ...newRecords)
tableFullData.splice(this.findRowIndexOf(tableFullData, row), 0, ...newRecords)
// 刷新单元格合并
mergeList.forEach(mergeItem => {
const { row: mergeRowIndex, rowspan: mergeRowspan } = mergeItem
if (mergeRowIndex > afIndex) {
mergeItem.row = mergeRowIndex + newRecords.length
} else if (mergeRowIndex + mergeRowspan > afIndex) {
mergeItem.rowspan = mergeRowspan + newRecords.length
}
})
}
}
}
editStore.insertList.unshift(...newRecords)
this.handleTableData(treeConfig && transform)
if (!(treeConfig && transform)) {
this.updateAfterDataIndex()
}
this.updateFooter()
this.cacheRowMap()
this.checkSelectionStatus()
if (this.scrollYLoad) {
this.updateScrollYSpace()
}
return this.$nextTick().then(() => {
this.updateCellAreas()
return this.recalculate()
}).then(() => {
return {
row: newRecords.length ? newRecords[newRecords.length - 1] : null,
rows: newRecords
}
})
},
/**
* 删除指定行数据
* 如果传 row 则删除一行
* 如果传 rows 则删除多行
* 如果为空则删除所有
*/
_remove (rows) {
const { afterFullData, tableFullData, tableFullTreeData, treeConfig, mergeList, editStore, checkboxOpts, selection, isInsertByRow, treeOpts } = this
const { transform } = treeOpts
const { actived, removeList, insertList } = editStore
const { checkField } = checkboxOpts
let rest = []
if (!rows) {
rows = tableFullData
} else if (!XEUtils.isArray(rows)) {
rows = [rows]
}
// 如果是新增,则保存记录
rows.forEach(row => {
if (!isInsertByRow(row)) {
removeList.push(row)
}
})
// 如果绑定了多选属性,则更新状态
if (!checkField) {
rows.forEach(row => {
const sIndex = this.findRowIndexOf(selection, row)
if (sIndex > -1) {
selection.splice(sIndex, 1)
}
})
}
// 从数据源中移除
if (tableFullData === rows) {
rows = rest = tableFullData.slice(0)
this.tableFullData = []
this.afterFullData = []
this.clearMergeCells()
} else {
// 如果为虚拟树
if (treeConfig && transform) {
rows.forEach((row) => {
const rowid = getRowid(this, row)
const matchObj = XEUtils.findTree(tableFullTreeData, item => rowid === getRowid(this, item), treeOpts)
if (matchObj) {
const rItems = matchObj.items.splice(matchObj.index, 1)
rest.push(rItems[0])
}
const afIndex = this.findRowIndexOf(afterFullData, row)
if (afIndex > -1) {
afterFullData.splice(afIndex, 1)
}
})
} else {
rows.forEach(row => {
const tfIndex = this.findRowIndexOf(tableFullData, row)
if (tfIndex > -1) {
const rItems = tableFullData.splice(tfIndex, 1)
rest.push(rItems[0])
}
const afIndex = this.findRowIndexOf(afterFullData, row)
if (afIndex > -1) {
// 刷新单元格合并
mergeList.forEach(mergeItem => {
const { row: mergeRowIndex, rowspan: mergeRowspan } = mergeItem
if (mergeRowIndex > afIndex) {
mergeItem.row = mergeRowIndex - 1
} else if (mergeRowIndex + mergeRowspan > afIndex) {
mergeItem.rowspan = mergeRowspan - 1
}
})
afterFullData.splice(afIndex, 1)
}
})
}
}
// 如果当前行被激活编辑,则清除激活状态
if (actived.row && this.findRowIndexOf(rows, actived.row) > -1) {
this.clearActived()
}
// 从新增中移除已删除的数据
rows.forEach(row => {
const iIndex = this.findRowIndexOf(insertList, row)
if (iIndex > -1) {
insertList.splice(iIndex, 1)
}
})
this.handleTableData(treeConfig && transform)
if (!(treeConfig && transform)) {
this.updateAfterDataIndex()
}
this.updateFooter()
this.cacheRowMap()
this.checkSelectionStatus()
if (this.scrollYLoad) {
this.updateScrollYSpace()
}
return this.$nextTick().then(() => {
this.updateCellAreas()
return this.recalculate()
}).then(() => {
return { row: rest.length ? rest[rest.length - 1] : null, rows: rest }
})
},
/**
* 删除复选框选中的数据
*/
_removeCheckboxRow () {
return this.remove(this.getCheckboxRecords()).then(params => {
this.clearCheckboxRow()
return params
})
},
/**
* 删除单选框选中的数据
*/
_removeRadioRow () {
const radioRecord = this.getRadioRecord()
return this.remove(radioRecord || []).then(params => {
this.clearRadioRow()
return params
})
},
/**
* 删除当前行选中的数据
*/
_removeCurrentRow () {
const currentRecord = this.getCurrentRecord()
return this.remove(currentRecord || []).then(params => {
this.clearCurrentRow()
return params
})
},
/**
* 获取表格数据集,包含新增、删除、修改
*/
_getRecordset () {
return {
insertRecords: this.getInsertRecords(),
removeRecords: this.getRemoveRecords(),
updateRecords: this.getUpdateRecords()
}
},
/**
* 获取新增的临时数据
*/
_getInsertRecords () {
const { treeConfig, tableFullTreeData, tableFullData, treeOpts } = this
const insertList = this.editStore.insertList
const insertRecords = []
if (insertList.length) {
// 如果为虚拟树
if (treeConfig && treeOpts.transform) {
insertList.forEach(row => {
const rowid = getRowid(this, row)
const matchObj = XEUtils.findTree(tableFullTreeData, item => rowid === getRowid(this, item), treeOpts)
if (matchObj) {
insertRecords.push(row)
}
})
} else {
insertList.forEach(row => {
if (tableFullData.indexOf(row) > -1) {
insertRecords.push(row)
}
})
}
}
return insertRecords
},
/**
* 获取已删除的数据
*/
_getRemoveRecords () {
return this.editStore.removeList
},
/**
* 获取更新数据
* 只精准匹配 row 的更改
* 如果是树表格,子节点更改状态不会影响父节点的更新状态
*/
_getUpdateRecords () {
const { keepSource, tableFullData, isUpdateByRow, treeConfig, treeOpts, editStore } = this
if (keepSource) {
const { actived } = editStore
const { row, column } = actived
if (row || column) {
this._syncActivedCell()
}
if (treeConfig) {
return XEUtils.filterTree(tableFullData, row => isUpdateByRow(row), treeOpts)
}
return tableFullData.filter(row => isUpdateByRow(row))
}
return []
},
/**
* 处理激活编辑
*/
handleActived (params, evnt) {
const { editStore, editOpts, tableColumn, editConfig, mouseConfig } = this
const { mode } = editOpts
const { actived } = editStore
const { row, column } = params
const { editRender } = column
const cell = params.cell = (params.cell || this.getCell(row, column))
const beforeEditMethod = editOpts.beforeEditMethod || editOpts.activeMethod
if (isEnableConf(editConfig) && isEnableConf(editRender) && cell) {
if (actived.row !== row || (mode === 'cell' ? actived.column !== column : false)) {
// 判断是否禁用编辑
let type = 'edit-disabled'
if (!beforeEditMethod || beforeEditMethod({ ...params, $table: this })) {
if (mouseConfig) {
this.clearSelected(evnt)
this.clearCellAreas(evnt)
this.clearCopyCellArea(evnt)
}
this.closeTooltip()
if (actived.column) {
this.clearActived(evnt)
}
type = 'edit-actived'
column.renderHeight = cell.offsetHeight
actived.args = params
actived.row = row
actived.column = column
if (mode === 'row') {
tableColumn.forEach(column => this._getColumnModel(row, column))
} else {
this._getColumnModel(row, column)
}
this.$nextTick(() => {
this.handleFocus(params, evnt)
})
}
this.emitEvent(type, {
row,
rowIndex: this.getRowIndex(row),
$rowIndex: this.getVMRowIndex(row),
column,
columnIndex: this.getColumnIndex(column),
$columnIndex: this.getVMColumnIndex(column)
}, evnt)
} else {
const { column: oldColumn } = actived
if (mouseConfig) {
this.clearSelected(evnt)
this.clearCellAreas(evnt)
this.clearCopyCellArea(evnt)
}
if (oldColumn !== column) {
const { model: oldModel } = oldColumn
if (oldModel.update) {
UtilTools.setCellValue(row, oldColumn, oldModel.value)
}
this.clearValidate()
}
column.renderHeight = cell.offsetHeight
actived.args = params
actived.column = column
setTimeout(() => {
this.handleFocus(params, evnt)
})
}
this.focus()
}
return this.$nextTick()
},
_getColumnModel (row, column) {
const { model, editRender } = column
if (editRender) {
model.value = UtilTools.getCellValue(row, column)
model.update = false
}
},
_setColumnModel (row, column) {
const { model, editRender } = column
if (editRender && model.update) {
UtilTools.setCellValue(row, column, model.value)
model.update = false
model.value = null
}
},
_syncActivedCell () {
const { tableColumn, editStore, editOpts } = this
const { actived } = editStore
const { row, column } = actived
if (row || column) {
if (editOpts.mode === 'row') {
tableColumn.forEach(column => this._setColumnModel(row, column))
} else {
this._setColumnModel(row, column)
}
}
},
_clearActived (evnt) {
// if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
// warnLog('vxe.error.delFunc', ['clearActived', 'clearEdit'])
// }
// 即将废弃
return this.clearEdit(evnt)
},
/**
* 清除激活的编辑
*/
_clearEdit (evnt) {
const { editStore } = this
const { actived } = editStore
const { row, column } = actived
if (row || column) {
this._syncActivedCell()
actived.args = null
actived.row = null
actived.column = null
this.updateFooter()
this.emitEvent('edit-closed', {
row,
rowIndex: this.getRowIndex(row),
$rowIndex: this.getVMRowIndex(row),
column,
columnIndex: this.getColumnIndex(column),
$columnIndex: this.getVMColumnIndex(column)
}, evnt)
}
return VXETable._valid ? this.clearValidate() : this.$nextTick()
},
_getActiveRecord () {
// if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
// warnLog('vxe.error.delFunc', ['getActiveRecord', 'getEditRecord'])
// }
// 即将废弃
return this.getEditRecord()
},
_getEditRecord () {
const { $el, editStore, afterFullData } = this
const { actived } = editStore
const { args, row } = actived
if (args && this.findRowIndexOf(afterFullData, row) > -1 && $el.querySelectorAll('.vxe-body--column.col--actived').length) {
return Object.assign({}, args)
}
return null
},
_isActiveByRow (row) {
// if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
// warnLog('vxe.error.delFunc', ['isActiveByRow', 'isEditByRow'])
// }
// 即将废弃
return this.isEditByRow(row)
},
/**
* 判断行是否为激活编辑状态
* @param {Row} row 行对象
*/
_isEditByRow (row) {
return this.editStore.actived.row === row
},
/**
* 处理聚焦
*/
handleFocus (params) {
const { row, column, cell } = params
const { editRender } = column
if (isEnableConf(editRender)) {
const compRender = VXETable.renderer.get(editRender.name)
let { autofocus, autoselect } = editRender
let inputElem
if (!autofocus && compRender) {
autofocus = compRender.autofocus
}
// 如果指定了聚焦 class
if (XEUtils.isFunction(autofocus)) {
inputElem = autofocus.call(this, params)
} else if (autofocus) {
inputElem = cell.querySelector(autofocus)
if (inputElem) {
inputElem.focus()
}
}
if (inputElem) {
if (autoselect) {
inputElem.select()
} else {
// 保持一致行为,光标移到末端
if (browse.msie) {
const textRange = inputElem.createTextRange()
textRange.collapse(false)
textRange.select()
}
}
} else {
// 显示到可视区中
this.scrollToRow(row, column)
}
}
},
_setActiveRow (row) {
// if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
// warnLog('vxe.error.delFunc', ['setActiveRow', 'setEditRow'])
// }
// 即将废弃
return this.setEditRow(row)
},
/**
* 激活行编辑
*/
_setEditRow (row) {
return this.setEditCell(row, XEUtils.find(this.visibleColumn, column => isEnableConf(column.editRender)))
},
_setActiveCell (row) {
// if (process.env.VUE_APP_VXE_TABLE_ENV === 'development') {
// warnLog('vxe.error.delFunc', ['setActiveCell', 'setEditCell'])
// }
// 即将废弃
return this.setEditCell(row)
},
/**
* 激活单元格编辑
*/
_setEditCell (row, fieldOrColumn) {
const { editConfig } = this
const column = XEUtils.isString(fieldOrColumn) ? this.getColumnByField(fieldOrColumn) : fieldOrColumn
if (row && column && isEnableConf(editConfig) && isEnableConf(column.editRender)) {
return this.scrollToRow(row, true).then(() => {
const cell = this.getCell(row, column)
if (cell) {
this.handleActived({ row, rowIndex: this.getRowIndex(row), column, columnIndex: this.getColumnIndex(column), cell, $table: this })
this.lastCallTime = Date.now()
}
})
}
return this.$nextTick()
},
/**
* 只对 trigger=dblclick 有效,选中单元格
*/
_setSelectCell (row, fieldOrColumn) {
const { tableData, editOpts, visibleColumn } = this
const column = XEUtils.isString(fieldOrColumn) ? this.getColumnByField(fieldOrColumn) : fieldOrColumn
if (row && column && editOpts.trigger !== 'manual') {
const rowIndex = this.findRowIndexOf(tableData, row)
if (rowIndex > -1) {
const cell = this.getCell(row, column)
const params = { row, rowIndex, column, columnIndex: visibleColumn.indexOf(column), cell }
this.handleSelected(params, {})
}
}
return this.$nextTick()
},
/**
* 处理选中源
*/
handleSelected (params, evnt) {
const { mouseConfig, mouseOpts, editOpts, editStore } = this
const { actived, selected } = editStore
const { row, column } = params
const isMouseSelected = mouseConfig && mouseOpts.selected
const selectMethod = () => {
if (isMouseSelected && (selected.row !== row || selected.column !== column)) {
if (actived.row !== row || (editOpts.mode === 'cell' ? actived.column !== column : false)) {
this.clearActived(evnt)
this.clearSelected(evnt)
this.clearCellAreas(evnt)
this.clearCopyCellArea(evnt)
selected.args = params
selected.row = row
selected.column = column
if (isMouseSelected) {
this.addColSdCls()
}
this.focus()
if (evnt) {
this.emitEvent('cell-selected', params, evnt)
}
}
}
return this.$nextTick()
}
return selectMethod()
},
/**
* 获取选中的单元格
*/
_getSelectedCell () {
const { args, column } = this.editStore.selected
if (args && column) {
return Object.assign({}, args)
}
return null
},
/**
* 清除所选中源状态
*/
_clearSelected () {
const { selected } = this.editStore
selected.row = null
selected.column = null
this.reColTitleSdCls()
this.reColSdCls()
return this.$nextTick()
},
reColTitleSdCls () {
const headerElem = this.elemStore['main-header-list']
if (headerElem) {
XEUtils.arrayEach(headerElem.querySelectorAll('.col--title-selected'), elem => DomTools.removeClass(elem, 'col--title-selected'))
}
},
reColSdCls () {
const cell = this.$el.querySelector('.col--selected')
if (cell) {
DomTools.removeClass(cell, 'col--selected')
}
},
addColSdCls () {
const { selected } = this.editStore
const { row, column } = selected
this.reColSdCls()
if (row && column) {
const cell = this.getCell(row, column)
if (cell) {
DomTools.addClass(cell, 'col--selected')
}
}
}
}
}