UNPKG

vue-fantable

Version:
1,769 lines (1,624 loc) 119 kB
import { cloneDeep, debounce, getValByUnit, isFunction, isNumber, scrollTo, isEmptyValue, isEmptyArray, isBoolean, isDefined, } from '@P/src/utils/index.js' import { initGroupColumns, clsName, getNotFixedTotalWidthByColumnKey, recursiveRemoveColumnByKey, setHeaderContextmenuOptions, setBodyContextmenuOptions, createEmptyRowData, isContextmenuPanelClicked, getRowKey, getColKeysByHeaderColumn, getColumnByColkey, getLeftmostColKey, isCellInSelectionRange, isClearSelectionByBodyCellRightClick, cellAutofill, isOperationColumn, getSelectionRangeData, getSelectionRangeKeys, getSelectionRangeIndexes, setColumnFixed, cancelColumnFixed } from './util/index' import { onBeforeCopy, onAfterCopy, onBeforePaste, onAfterPaste, onBeforeCut, onAfterCut, onBeforeDelete, onAfterDelete } from './util/clipboard' import { createLocale } from '@P/src/locale/index' import { KEY_CODES, MOUSE_EVENT_CLICK_TYPE } from '@P/src/utils/constant' import { getScrollbarWidth } from '@P/src/utils/scroll-bar' import { requestAnimationTimeout, cancelAnimationTimeout } from '@P/src/utils/request-animation-timeout' import { isInputKeyCode } from '@P/src/utils/event-key-codes' import Hooks from '@P/src/utils/hooks-manager' import { getMouseEventClickType } from '@P/src/utils/mouse-event' import { GLOBAL_EVENT } from '@P/events/global-events' import { COMPS_NAME, HOOKS_NAME, COMPS_CUSTOM_ATTRS, INSTANCE_METHODS, CELL_SELECTION_DIRECTION, LOCALE_COMP_NAME, CONTEXTMENU_TYPES, CONTEXTMENU_NODE_TYPES, AUTOFILLING_DIRECTION, CURRENT_CELL_SELECTION_TYPES, COLUMN_FIXED_TYPE } from './util/constant' import ColGroup from './colgroup/index.jsx' import TableHeader from './header/index.jsx' import TableBody from './body/index.jsx' import TableFooter from './footer/index.jsx' import EditInput from './editor/index.jsx' import Selection from './selection/index.jsx' import clickoutside from '@P/src/directives/clickoutside' import VueDomResizeObserver from '@P/src/components/resize-observer/index' import VeContextmenu from '@P/ve-contextmenu/ve-contextmenu.js' import ColumnResizer from './column-resizer/index.jsx' // import eventCenter from '@P/events/event-center' import mitt from 'mitt' const $t = createLocale(LOCALE_COMP_NAME) export default { name: COMPS_NAME.FAN_TABLE, directives: { 'click-outside': clickoutside, }, components: { VueDomResizeObserver, ColumnResizer, ColGroup, TableHeader, TableBody, TableFooter, EditInput, Selection, VeContextmenu }, provide() { return { eventCenter: this.eventCenter } }, props: { tableData: { required: true, type: Array, }, footerData: { type: Array, default: function () { return [] }, }, showHeader: { type: Boolean, default: true, }, columns: { type: Array, required: true, }, // row key field for row expand、row selection rowKeyFieldName: { type: String, default: null, }, // table scroll width scrollWidth: { type: [Number, String], default: null, }, // table max height maxHeight: { type: [Number, String], default: null, }, // fixed header fixedHeader: { type: Boolean, default: true, }, // fixed footer fixedFooter: { type: Boolean, default: true, }, // border around borderAround: { type: Boolean, default: true, }, // border horizontal borderX: { type: Boolean, default: true, }, // border vertical borderY: { type: Boolean, default: false, }, // event custom option eventCustomOption: { type: Object, default: function () { return null }, }, // cell style option cellStyleOption: { type: Object, default: function () { return null }, }, // cell span option cellSpanOption: { type: Object, default: function () { return null }, }, // row style option rowStyleOption: { type: Object, default: function () { return null }, }, /* virtual scroll option { enable:true, bufferCount:10, // 缓冲的数据 minRowHeight:40, scrolling:(startRowIndex,visibleStartIndex,visibleEndIndex,visibleAboveCount,visibleBelowCount)=>{} } */ virtualScrollOption: { type: Object, default: null, }, // sort option sortOption: { type: Object, default: function () { return null }, }, // expand row option expandOption: { type: Object, default: function () { return null }, }, // checkbox option checkboxOption: { type: Object, default: function () { return null }, }, // radio option radioOption: { type: Object, default: function () { return null }, }, // cell selection option cellSelectionOption: { type: Object, default: function () { return null }, }, // cell autofill option cellAutofillOption: { type: [Object, Boolean], default: function () { return null }, }, // edit option editOption: { type: Object, default: function () { return null }, }, // column hidden option columnHiddenOption: { type: Object, default: function () { return null }, }, // contextmenu header option contextmenuHeaderOption: { type: Object, default: function () { return null }, }, // contextmenu body option contextmenuBodyOption: { type: Object, default: function () { return null }, }, // clipboard option clipboardOption: { type: Object, default: function () { return null }, }, // column width resize option columnWidthResizeOption: { type: Object, default: function () { return null }, }, }, data() { return { // Hooks instance eventCenter: mitt(), hooks: {}, // is parent rendered parentRendered: false, // table viewport width except scroll bar width tableViewportWidth: 0, /* 列配置变化次数 依赖columns 配置渲染,都需要重新计算:粘性布局时,重新触发 on-dom-resize-change 事件 */ columnsOptionResetTime: 0, tableRootRef: 'tableRootRef', tableContainerWrapperRef: 'tableContainerWrapperRef', tableContainerRef: 'tableContainerRef', tableRef: 'tableRef', tableBodyRef: 'tableBodyRef', tableContentWrapperRef: 'tableContentWrapperRef', virtualPhantomRef: 'virtualPhantomRef', editInputRef: 'editInputRef', cellSelectionRef: 'cellSelectionRef', contextmenuRef: 'contextmenuRef', cloneColumns: [], // is group header isGroupHeader: false, /* header rows created by groupColumns */ headerRows: [ /* { rowHeight:40 } */ ], /* footer rows created by footerData */ footerRows: [ /* { rowHeight:40 } */ ], // colGroups colgroups: [], // groupColumns groupColumns: [], /* 存储当前隐藏列信息 hidden columns */ hiddenColumns: [], /* // virtual scroll positions(非响应式) virtualScrollPositions = [ { rowKey: "", // 当前行数据 rowKey top: 0, // 距离上一个项的高度 bottom: 100, // 距离下一个项的高度 height: 100 // 自身高度 } ], */ // virtual scroll visible data virtualScrollVisibleData: [], // virtual scroll visible indexs virtualScrollVisibleIndexs: { start: -1, end: -1, }, // default virtual scroll buffer scale defaultVirtualScrollBufferScale: 1, // default virtual scroll min row height defaultVirtualScrollMinRowHeight: 40, // default placeholder per scrolling row count defaultPlaceholderPerScrollingRowCount: 8, // 起始索引 virtualScrollStartIndex: 0, // preview virtual scroll start index previewVirtualScrollStartIndex: 0, // 结束索引 virtualScrollEndIndex: 0, // is scrolling showVirtualScrollingPlaceholder: false, // disable pointer events timeout id disablePointerEventsTimeoutId: null, // is scrolling left isLeftScrolling: false, // is scrolling right isRightScrolling: false, // is scrolling vertically isVerticalScrolling: false, // has horizontal scroll bar hasXScrollBar: false, // has vertical scroll bar hasYScrollBar: false, // scroll bar width scrollBarWidth: 0, // preview table container scrollLeft (处理左列或右列固定效果) previewTableContainerScrollLeft: null, // header cell selection colKeys headerIndicatorColKeys: { startColKey: '', startColKeyIndex: -1, endColKey: '', endColKeyIndex: -1, }, // body indicator rowKeys bodyIndicatorRowKeys: { startRowKey: '', startRowKeyIndex: -1, endRowKey: '', endRowKeyIndex: -1, }, // cell selection data cellSelectionData: { currentCell: { rowKey: '', colKey: '', rowIndex: -1, }, normalEndCell: { rowKey: '', colKey: '', rowIndex: -1, }, autoFillEndCell: { rowKey: '', colKey: '', }, }, // cell selection range data cellSelectionRangeData: { leftColKey: '', rightColKey: '', topRowKey: '', bottomRowKey: '', }, // is header cell mousedown isHeaderCellMousedown: false, // is body cell mousedown isBodyCellMousedown: false, // is body operation column mousedown isBodyOperationColumnMousedown: false, // is cell selection corner mousedown isAutofillStarting: false, // autofilling direction autofillingDirection: null, // current cell selection type currentCellSelectionType: '', /* table offest height(开启虚拟滚动时使用) 1、当 :max-height="500" 时使用 max-height 2、当 max-height="calc(100vh - 210px)" 或者 max-height="80%" 时使用 tableOffestHeight */ tableOffestHeight: 0, // table height tableHeight: 0, // highlight row key highlightRowKey: '', /* editing cell */ editingCell: { rowKey: '', colKey: '', row: null, column: null, }, // 编辑单元格每次开始编辑前的初始值 editorInputStartValue: '', /* 是否允许按下方向键时,停止编辑并移动选中单元格。当双击可编辑单元格或者点击输入文本框时设置为false值 像excel一样:如果直接在可编辑单元格上输入内容后,按下上、下、左、右按键可以直接选中其他单元格,并停止当前单元格编辑状态 like Excel:If you directly enter content in an editable cell, press the up, down, left and right buttons to directly select other cells and stop editing the current cell */ enableStopEditing: true, // contextmenu event target contextmenuEventTarget: '', // contextmenu options contextmenuOptions: [], // column resize cursor isColumnResizerHover: false, // is column resizing isColumnResizing: false, } }, computed: { // actual render table data actualRenderTableData() { return this.isVirtualScroll ? this.virtualScrollVisibleData : this.tableData }, // return row keys allRowKeys() { let result = [] const { tableData, rowKeyFieldName } = this if (rowKeyFieldName) { result = tableData.map((x) => { return x[rowKeyFieldName] }) } return result }, // virtual scroll buffer count virtualScrollBufferCount() { let result = 0 const { virtualScrollOption, defaultVirtualScrollBufferScale, virtualScrollVisibleCount, } = this if (virtualScrollOption) { const { bufferScale } = virtualScrollOption const realBufferScale = isNumber(bufferScale) && bufferScale > 0 ? bufferScale : defaultVirtualScrollBufferScale result = realBufferScale * virtualScrollVisibleCount } return result }, // virtual scroll visible count virtualScrollVisibleCount() { let result = 0 const { isVirtualScroll, virtualScrollOption, defaultVirtualScrollMinRowHeight, maxHeight, tableOffestHeight, } = this if (isVirtualScroll && maxHeight) { const minRowHeight = isNumber(virtualScrollOption.minRowHeight) ? virtualScrollOption.minRowHeight : defaultVirtualScrollMinRowHeight if (isNumber(maxHeight)) { result = Math.ceil(maxHeight / minRowHeight) } else if (tableOffestHeight) { // 修复当动态高度 当 max-height="calc(100vh - 210px)" 或者 max-height="80%" 时无法计算的问题 result = Math.ceil(tableOffestHeight / minRowHeight) } } return result }, // table container wrapper style tableContainerWrapperStyle() { return { width: '100%', } }, // table container style tableContainerStyle() { const maxHeight = getValByUnit(this.maxHeight) let tableContainerHeight = null if (this.isVirtualScroll) { if (maxHeight) { tableContainerHeight = maxHeight } else { console.error( "maxHeight prop is required when 'virtualScrollOption.enable = true'", ) } } else { /* fixed:虚拟滚动表格行展开的 fan-table 存在固定头时(sticky 冲突),表格样式错乱的问题 fixed:When there is a fixed header in the fan-table expanded by the row of the virtual rolling table(header sticky conflict),Incorrect table presentation */ const { tableHeight, hasXScrollBar } = this tableContainerHeight = tableHeight /* 有横向滚动条时,表格高度需要加上滚动条的宽度 When there is a horizontal scroll bar, the table height needs to be added with the width of the scroll bar */ if (hasXScrollBar) { tableContainerHeight += this.getScrollBarWidth() } tableContainerHeight = getValByUnit(tableContainerHeight) } return { 'max-height': maxHeight, // if virtual scroll height: tableContainerHeight, } }, // table style tableStyle() { return { width: getValByUnit(this.scrollWidth), } }, // table class tableClass() { const borderY = clsName('border-y') return { [clsName('border-x')]: this.borderX, [borderY]: this.borderY, } }, // table container class tableContainerClass() { const { isVirtualScroll, isLeftScrolling, isRightScrolling, isVerticalScrolling, isCellEditing, isAutofillStarting, enableCellSelection, } = this const virtualScroll = clsName('virtual-scroll') const leftScrolling = clsName('container-left-scrolling') const rightScrolling = clsName('container-right-scrolling') const verticalScroll = clsName('container-vertical-scrolling') const cellEditing = clsName('is-cell-editing') const autofilling = clsName('autofilling') const cellSelection = clsName('enable-cell-selection') const result = { [clsName('container')]: true, [virtualScroll]: isVirtualScroll, [leftScrolling]: isLeftScrolling, [rightScrolling]: isRightScrolling, [verticalScroll]: isVerticalScrolling, [cellEditing]: isCellEditing, [autofilling]: isAutofillStarting, // 如果开启单元格选择,则关闭 user-select [cellSelection]: enableCellSelection, } return result }, // table body class tableBodyClass() { let result = null const { rowStyleOption } = this let hoverHighlight = true let clickHighlight = true let stripe = false if (rowStyleOption) { hoverHighlight = rowStyleOption.hoverHighlight clickHighlight = rowStyleOption.clickHighlight stripe = rowStyleOption.stripe } const clsStripe = clsName('stripe') const rowHover = clsName('row-hover') result = { [clsStripe]: stripe === true, // 默认不开启 [rowHover]: hoverHighlight !== false, // 默认开启 [clsName('row-highlight')]: clickHighlight !== false, // 默认开启 } return result }, // is virtual scroll isVirtualScroll() { const { virtualScrollOption } = this return virtualScrollOption && virtualScrollOption.enable }, // has fixed column hasFixedColumn() { return this.colgroups.some( (x) => x.fixed === COLUMN_FIXED_TYPE.LEFT || x.fixed === COLUMN_FIXED_TYPE.RIGHT, ) }, // has left fixed column hasLeftFixedColumn() { return this.colgroups.some( (x) => x.fixed === COLUMN_FIXED_TYPE.LEFT, ) }, // has right fixed column hasRightFixedColumn() { return this.colgroups.some( (x) => x.fixed === COLUMN_FIXED_TYPE.RIGHT, ) }, // is editing cell isCellEditing() { const { editingCell } = this return ( !isEmptyValue(editingCell.rowKey) && !isEmptyValue(editingCell.colKey) ) }, // has edit column hasEditColumn() { return this.colgroups.some((x) => x.edit) }, // enable header contextmenu enableHeaderContextmenu() { let result = false const { contextmenuHeaderOption } = this if (contextmenuHeaderOption) { const { contextmenus } = contextmenuHeaderOption if (Array.isArray(contextmenus) && contextmenus.length) { result = true } } return result }, // enable body contextmenu enableBodyContextmenu() { let result = false const { contextmenuBodyOption } = this if (contextmenuBodyOption) { const { contextmenus } = contextmenuBodyOption if (Array.isArray(contextmenus) && contextmenus.length) { result = true } } return result }, // contextmenu type contextMenuType() { if (this.headerIndicatorColKeys.startColKeyIndex > -1) { return CONTEXTMENU_TYPES.HEADER_CONTEXTMENU } else { return CONTEXTMENU_TYPES.BODY_CONTEXTMENU } }, /* enable cell selection 单元格编辑、剪贴板都依赖单元格选择 */ enableCellSelection() { let result = true const { cellSelectionOption, rowKeyFieldName } = this if (isEmptyValue(rowKeyFieldName)) { result = false } else if ( cellSelectionOption && isBoolean(cellSelectionOption.enable) && cellSelectionOption.enable === false ) { result = false } return result }, // enable clipboard enableClipboard() { return this.rowKeyFieldName }, // eanble width resize enableColumnResize() { let result = false const { columnWidthResizeOption } = this if (columnWidthResizeOption) { const { enable } = columnWidthResizeOption if (isBoolean(enable)) { result = enable } } return result }, // header total height headerTotalHeight() { let result = 0 if (this.showHeader) { result = this.headerRows.reduce((total, currentVal) => { return currentVal.rowHeight + total }, 0) } return result }, // footer total height footerTotalHeight() { return this.footerRows.reduce((total, currentVal) => { return currentVal.rowHeight + total }, 0) }, }, watch: { // watch clone table data tableData: { handler(newVal, oldVal) { this.initVirtualScrollPositions() // 第一次不需要触发,仅数据变更触发 if (oldVal) { this.initVirtualScroll() } }, deep: true, immediate: true, }, allRowKeys: { handler(newVal) { if (Array.isArray(newVal)) { const { currentCell } = this.cellSelectionData // 行被移除,清空单元格选中 if (currentCell.rowIndex > -1) { if (newVal.indexOf(currentCell.rowKey) === -1) { this.clearCellSelectionCurrentCell() } } } }, immediate: false, }, columns: { handler(newVal, oldVal) { this.initColumns() this.initGroupColumns() this.initColumnWidthByColumnResize() // 排除首次 if (newVal !== oldVal && oldVal) { this.columnsOptionResetTime++ // 需要等待 initColumns 和 initGroupColumns 先执行 this.initScrolling() } }, immediate: true, }, cloneColumns: { handler() { this.initGroupColumns() // 右键(取消)固定列会操作 cloneColumns this.initColumnWidthByColumnResize() this.columnsOptionResetTime++ // 需要等待 initColumns 和 initGroupColumns 先执行 this.initScrolling() }, immediate: false, }, // group columns change watch groupColumns: { handler(val) { if (!isEmptyArray(val)) { this.initHeaderRows() } }, immediate: true, }, // footer data footerData: { handler(val) { if (!isEmptyArray(val)) { this.initFooterRows() } }, immediate: true, }, /* watch virtualScrollOption enable 允许按需开启虚拟滚动 */ 'virtualScrollOption.enable': { handler(newVal) { // enable virtual scroll if (newVal) { this.initVirtualScrollPositions() this.initVirtualScroll() } else { // disable virtual scroll // clear table content top value this.setTableContentTopValue({ top: 0 }) } }, immediate: false, }, // is auto fill starting isAutofillStarting: { handler(val) { if (!val) { this.setCellSelectionByAutofill() this.clearCellSelectionAutofillEndCell() } }, }, // watch current cell 'cellSelectionData.currentCell': { handler: function () { this.setCurrentCellSelectionType() }, deep: true, immediate: true, }, // watch normal end cell 'cellSelectionData.normalEndCell': { handler: function () { this.setCurrentCellSelectionType() }, deep: true, immediate: true, }, // watch header indicator colKeys headerIndicatorColKeys: { handler: function () { this.setRangeCellSelectionByHeaderIndicator() }, deep: true, }, // watch body indicator rowKeys bodyIndicatorRowKeys: { handler: function () { this.setRangeCellSelectionByBodyIndicator() }, deep: true, }, }, created() { // bug fixed #467 this.debouncedBodyCellWidthChange = debounce( this.bodyCellWidthChange, 0, ) }, mounted() { this.parentRendered = true // set contextmenu event target this.contextmenuEventTarget = this.$el.querySelector( `.${clsName('content')}`, ) // create hook instance this.hooks = new Hooks() // receive sort change this.eventCenter.on(GLOBAL_EVENT.SORT_CHANGE_AFTER, (params) => { this.updateColgroupsBySortChange(params) }) // receive row selected change this.eventCenter.on(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_CHANGE_TABLE, (params) => { this.selectedAllChange(params) }) // receive selected all info this.eventCenter.on(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_INFO, (params) => { this.setSelectedAllInfo(params) }) // receive multiple header row height change this.eventCenter.on(GLOBAL_EVENT.HEADER_ROW_HEIGHT_CHANGE, ({ rowIndex, height }) => { this.headerRowHeightChange({ rowIndex, height }) }, ) // receive virtual scroll row height change this.eventCenter.on(GLOBAL_EVENT.BODY_ROW_HEIGHT_CHANGE, ({ rowKey, height }) => { this.bodyRowHeightChange({ rowKey, height }) }) // receive footer row height change this.eventCenter.on(GLOBAL_EVENT.FOOTER_ROW_HEIGHT_CHANGE, ({ rowIndex, height }) => { this.footRowHeightChange({ rowIndex, height }) }, ) // receive body cell click this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_CLICK, (params) => { this.bodyCellClick(params) }) // receive body cell mouseover this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEOVER, (params) => { this.bodyCellMouseover(params) }) // receive body cell mousedown this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEDOWN, (params) => { this.bodyCellMousedown(params) }) // receive body cell mousemove this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEMOVE, (params) => { this.bodyCellMousemove(params) }) // receive body cell mouseup this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_MOUSEUP, (params) => { this.bodyCellMouseup(params) }) // receive selection corner mousedown this.eventCenter.on(GLOBAL_EVENT.SELECTION_CORNER_MOUSEDOWN, (params) => { this.cellSelectionCornerMousedown(params) }) // receive selection corner mouseup this.eventCenter.on(GLOBAL_EVENT.SELECTION_CORNER_MOUSEUP, (params) => { this.cellSelectionCornerMouseup(params) }) // autofilling direction change this.eventCenter.on(GLOBAL_EVENT.AUTOFILLING_DIRECTION_CHANGE, (params) => { this.autofillingDirectionChange(params) }) // receive body cell contextmenu(right click) this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_CONTEXTMENU, (params) => { this.bodyCellContextmenu(params) }) // receive body cell double click this.eventCenter.on(GLOBAL_EVENT.BODY_CELL_DOUBLE_CLICK, (params) => { this.bodyCellDoubleClick(params) }) // receive header cell contextmenu(right click) this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_CLICK, (params) => { this.headerCellClick(params) }) // receive header cell contextmenu(right click) this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_CONTEXTMENU, (params) => { this.headerCellContextmenu(params) }) // receive header cell mousedown this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSEDOWN, (params) => { this.headerCellMousedown(params) }) // receive header cell mouseover this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSEOVER, (params) => { this.headerCellMouseover(params) }) // receive header cell mousemove this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSEMOVE, (params) => { this.headerCellMousemove(params) }) // receive header cell mouseleave this.eventCenter.on(GLOBAL_EVENT.HEADER_CELL_MOUSELEAVE, (params) => { this.headerCellMouseleave(params) }) // add key down event listener document.addEventListener('keydown', this.dealKeydownEvent) // init scrolling this.initScrolling() }, unmounted() { // remove key down event listener document.removeEventListener('keydown', this.dealKeydownEvent) }, methods: { // int header rows initHeaderRows() { const { groupColumns } = this if (Array.isArray(groupColumns)) { this.headerRows = groupColumns.map(() => { return { rowHeight: 0 } }) } }, // int footer rows initFooterRows() { const { footerData } = this if (Array.isArray(footerData)) { this.footerRows = footerData.map(() => { return { rowHeight: 0 } }) } }, // header tr height resize headerRowHeightChange({ rowIndex, height }) { this.headerRows.splice(rowIndex, 1, { rowHeight: height }) }, // footer row height resize footRowHeightChange({ rowIndex, height }) { this.footerRows.splice(rowIndex, 1, { rowHeight: height }) }, // body cell width change bodyCellWidthChange(colWidths) { this.colgroups = this.colgroups.map((item) => { item._realTimeWidth = colWidths.get(item.key) return item }) this.hooks.triggerHook(HOOKS_NAME.TABLE_CELL_WIDTH_CHANGE) }, // set column width for column resize setColumnWidth({ colKey, width }) { this.colgroups = this.colgroups.map((item) => { if (item.key === colKey) { item._columnResizeWidth = width } return item }) this.$nextTick(() => { this.setScrollBarStatus() }) this.hooks.triggerHook(HOOKS_NAME.TABLE_CELL_WIDTH_CHANGE) }, // update colgroups by sort change updateColgroupsBySortChange(sortColumns) { this.colgroups = this.colgroups.map((item) => { // update colgroups by sort columns if (Object.keys(sortColumns).indexOf(item.field) > -1) { item.sortBy = sortColumns[item.field] } return item }) }, // init column width by column resize initColumnWidthByColumnResize() { const { enableColumnResize } = this const columnDefaultWidth = 50 if (enableColumnResize) { this.colgroups = this.colgroups.map((item) => { let columnWidth = columnDefaultWidth if (isNumber(item.width)) { columnWidth = item.width } item._columnResizeWidth = columnWidth return item }) } }, // init columns initColumns() { const { columnHiddenOption } = this if (columnHiddenOption) { const { defaultHiddenColumnKeys } = columnHiddenOption if (!isEmptyArray(defaultHiddenColumnKeys)) { this.hiddenColumns = defaultHiddenColumnKeys } } this.showOrHideColumns() }, // show or hide columns showOrHideColumns() { let cloneColumns = cloneDeep(this.columns) cloneColumns = cloneColumns.map((col) => { // 操作列默认左固定 if (col.operationColumn) { col.fixed = COLUMN_FIXED_TYPE.LEFT } return col }) const { hiddenColumns } = this if (!isEmptyArray(hiddenColumns)) { // recursive remove column key hiddenColumns.forEach((key) => { cloneColumns = recursiveRemoveColumnByKey( cloneColumns, key, ) }) } this.cloneColumns = cloneColumns }, // 初始化分组表头 initGroupColumns() { const result = initGroupColumns(this.cloneColumns) // set is group header this.isGroupHeader = result.isGroupHeader // set colgroups this.colgroups = result.colgroups // set groupColumns this.groupColumns = result.groupColumns }, // scroll bar width getScrollBarWidth() { let result = 0 const { scrollBarWidth } = this if (scrollBarWidth) { result = scrollBarWidth } else { result = getScrollbarWidth() this.scrollBarWidth = result } return result }, /* * @selectedAllChange * @desc selected all change * @param {bool} isSelected - is selected */ selectedAllChange({ isSelected }) { this.eventCenter.emit(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_CHANGE_BODY, { isSelected, }, ) }, /* * @setSelectedAllInfo * @desc set selected all info * @param {bool} isSelected - is selected * @param {bool} isIndeterminate - is indeterminate */ setSelectedAllInfo({ isSelected, isIndeterminate }) { this.eventCenter.emit(GLOBAL_EVENT.CHECKBOX_SELECTED_ALL_INFO_CHECKBOX, { isSelected, isIndeterminate, }, ) }, // cell selection current cell change cellSelectionCurrentCellChange({ rowKey, colKey }) { this.cellSelectionData.currentCell.colKey = colKey this.cellSelectionData.currentCell.rowKey = rowKey this.cellSelectionData.currentCell.rowIndex = this.allRowKeys.indexOf(rowKey) }, // cell selection end cell change cellSelectionNormalEndCellChange({ rowKey, colKey }) { this.cellSelectionData.normalEndCell.colKey = colKey this.cellSelectionData.normalEndCell.rowKey = rowKey this.cellSelectionData.normalEndCell.rowIndex = this.allRowKeys.indexOf(rowKey) }, // cell selection auto fill cell change cellSelectionAutofillCellChange({ rowKey, colKey }) { this.cellSelectionData.autoFillEndCell.colKey = colKey this.cellSelectionData.autoFillEndCell.rowKey = rowKey }, // clear cell selection current cell clearCellSelectionCurrentCell() { this.cellSelectionCurrentCellChange({ rowKey: '', colKey: '', rowIndex: -1, }) }, // clear cell selection normal end cell clearCellSelectionNormalEndCell() { this.cellSelectionNormalEndCellChange({ rowKey: '', colKey: '', rowIndex: -1, }) }, // clear cell selection autofill end cell clearCellSelectionAutofillEndCell() { this.cellSelectionAutofillCellChange({ rowKey: '', colKey: '' }) }, // header indicator colKeys change headerIndicatorColKeysChange({ startColKey, endColKey }) { const { colgroups } = this this.headerIndicatorColKeys.startColKey = startColKey this.headerIndicatorColKeys.startColKeyIndex = colgroups.findIndex( (x) => x.key === startColKey, ) this.headerIndicatorColKeys.endColKey = endColKey this.headerIndicatorColKeys.endColKeyIndex = colgroups.findIndex( (x) => x.key === endColKey, ) }, // clear header indicator colKeys clearHeaderIndicatorColKeys() { this.headerIndicatorColKeys.startColKey = '' this.headerIndicatorColKeys.startColKeyIndex = -1 this.headerIndicatorColKeys.endColKey = '' this.headerIndicatorColKeys.endColKeyIndex = -1 }, // body indicator rowKeys change bodyIndicatorRowKeysChange({ startRowKey, endRowKey }) { const { allRowKeys } = this this.bodyIndicatorRowKeys.startRowKey = startRowKey this.bodyIndicatorRowKeys.startRowKeyIndex = allRowKeys.indexOf(startRowKey) this.bodyIndicatorRowKeys.endRowKey = endRowKey this.bodyIndicatorRowKeys.endRowKeyIndex = allRowKeys.indexOf(endRowKey) }, // clear body indicator RowKeys clearBodyIndicatorRowKeys() { this.bodyIndicatorRowKeys.startRowKey = '' this.bodyIndicatorRowKeys.startRowKeyIndex = -1 this.bodyIndicatorRowKeys.endRowKey = '' this.bodyIndicatorRowKeys.endRowKeyIndex = -1 }, // set cell selection by autofill setCellSelectionByAutofill() { const { cellAutofillOption, cellSelectionRangeData, colgroups, allRowKeys, autofillingDirection, currentCellSelectionType, } = this const { autoFillEndCell, currentCell } = this.cellSelectionData const { rowKey, colKey } = autoFillEndCell if (isEmptyValue(rowKey) || isEmptyValue(colKey)) { return false } let currentCellData = {} let normalEndCellData = {} const { leftColKey, rightColKey, topRowKey, bottomRowKey } = cellSelectionRangeData // cell selection range auto fill if ( currentCellSelectionType === CURRENT_CELL_SELECTION_TYPES.RANGE ) { if ( !isCellInSelectionRange({ cellData: autoFillEndCell, cellSelectionRangeData, colgroups, allRowKeys, }) ) { if (autofillingDirection === AUTOFILLING_DIRECTION.RIGHT) { currentCellData = { rowKey: topRowKey, colKey: leftColKey, } normalEndCellData = { rowKey: bottomRowKey, colKey } } else if ( autofillingDirection === AUTOFILLING_DIRECTION.DOWN ) { currentCellData = { rowKey: topRowKey, colKey: leftColKey, } normalEndCellData = { rowKey, colKey: rightColKey } } else if ( autofillingDirection === AUTOFILLING_DIRECTION.UP ) { currentCellData = { rowKey, colKey: leftColKey, } normalEndCellData = { rowKey: bottomRowKey, colKey: rightColKey, } } else if ( autofillingDirection === AUTOFILLING_DIRECTION.LEFT ) { currentCellData = { rowKey: topRowKey, colKey } normalEndCellData = { rowKey: bottomRowKey, colKey: rightColKey, } } } else { // return if within the range return false } } else if ( // cell selection single auto fill currentCellSelectionType === CURRENT_CELL_SELECTION_TYPES.SINGLE ) { if ( currentCell.rowKey !== rowKey || currentCell.colKey !== colKey ) { if (autofillingDirection === AUTOFILLING_DIRECTION.RIGHT) { currentCellData = { rowKey, colKey: leftColKey, } normalEndCellData = { rowKey, colKey, } } else if ( autofillingDirection === AUTOFILLING_DIRECTION.DOWN ) { currentCellData = { rowKey: topRowKey, colKey: leftColKey, } normalEndCellData = { rowKey, colKey: leftColKey, } } else if ( autofillingDirection === AUTOFILLING_DIRECTION.UP ) { currentCellData = { rowKey, colKey: leftColKey, } normalEndCellData = { rowKey: bottomRowKey, colKey: leftColKey, } } else if ( autofillingDirection === AUTOFILLING_DIRECTION.LEFT ) { currentCellData = { rowKey, colKey, } normalEndCellData = { rowKey, colKey: rightColKey, } } } else { // return if within the range return false } } const cellAutofillParams = { tableData: this.tableData, allRowKeys: this.allRowKeys, colgroups: this.colgroups, rowKeyFieldName: this.rowKeyFieldName, direction: autofillingDirection, currentCellSelectionType, cellSelectionRangeData, nextCurrentCell: currentCellData, nextNormalEndCell: normalEndCellData, } if (cellAutofillOption) { const { beforeAutofill, afterAutofill } = cellAutofillOption if (isFunction(beforeAutofill)) { // before autofill const autofillResponse = cellAutofill({ isReplaceData: false, ...cellAutofillParams, }) const callback = beforeAutofill(autofillResponse) if (isBoolean(callback) && !callback) { return false } } // after autofill const autofillResponse = cellAutofill({ isReplaceData: true, ...cellAutofillParams, }) if (isFunction(afterAutofill)) { afterAutofill(autofillResponse) } } if (!isEmptyValue(currentCellData.rowKey)) { this.cellSelectionCurrentCellChange({ rowKey: currentCellData.rowKey, colKey: currentCellData.colKey, }) } if (!isEmptyValue(normalEndCellData.rowKey)) { this.cellSelectionNormalEndCellChange({ rowKey: normalEndCellData.rowKey, colKey: normalEndCellData.colKey, }) } }, // cell selection range data change cellSelectionRangeDataChange(newData) { this.cellSelectionRangeData = Object.assign( this.cellSelectionRangeData, newData, ) }, // autofilling direction change autofillingDirectionChange(direction) { this.autofillingDirection = direction }, // set current cell selection type setCurrentCellSelectionType() { const { currentCell, normalEndCell } = this.cellSelectionData let result if ( isEmptyValue(currentCell.rowKey) || isEmptyValue(currentCell.colKey) ) { result = '' } else { if ( !isEmptyValue(normalEndCell.rowKey) && !isEmptyValue(normalEndCell.colKey) ) { result = CURRENT_CELL_SELECTION_TYPES.RANGE } else { result = CURRENT_CELL_SELECTION_TYPES.SINGLE } } this.currentCellSelectionType = result }, // deal keydown event dealKeydownEvent(event) { const { colgroups, cellSelectionData, enableStopEditing, isCellEditing, } = this const { keyCode, ctrlKey, shiftKey, altKey } = event const { rowKey, colKey } = cellSelectionData.currentCell const currentColumn = colgroups.find((x) => x.key === colKey) if (!isEmptyValue(rowKey) && !isEmptyValue(colKey)) { switch (keyCode) { case KEY_CODES.TAB: { let direction if (shiftKey) { direction = CELL_SELECTION_DIRECTION.LEFT } else { direction = CELL_SELECTION_DIRECTION.RIGHT } this.selectCellByDirection({ direction, }) this.clearCellSelectionNormalEndCell() this[INSTANCE_METHODS.STOP_EDITING_CELL]() event.preventDefault() break } case KEY_CODES.ARROW_LEFT: { const direction = CELL_SELECTION_DIRECTION.LEFT if (enableStopEditing) { this.selectCellByDirection({ direction, }) this.clearCellSelectionNormalEndCell() this[INSTANCE_METHODS.STOP_EDITING_CELL]() event.preventDefault() } break } case KEY_CODES.ARROW_RIGHT: { const direction = CELL_SELECTION_DIRECTION.RIGHT if (enableStopEditing) { this.selectCellByDirection({ direction, }) this.clearCellSelectionNormalEndCell() this[INSTANCE_METHODS.STOP_EDITING_CELL]() event.preventDefault() } break } case KEY_CODES.ARROW_UP: { const direction = CELL_SELECTION_DIRECTION.UP if (enableStopEditing) { this.selectCellByDirection({ direction, }) this.clearCellSelectionNormalEndCell() this[INSTANCE_METHODS.STOP_EDITING_CELL]() event.preventDefault() } break } case KEY_CODES.ARROW_DOWN: { const direction = CELL_SELECTION_DIRECTION.DOWN if (enableStopEditing) { this.selectCellByDirection({ direction, }) this.clearCellSelectionNormalEndCell() this[INSTANCE_METHODS.STOP_EDITING_CELL]() event.preventDefault() } break } case KEY_CODES.ENTER: { let direction // add new line if (altKey) { const editInputEditor = this.$refs[this.editInputRef] editInputEditor.textareaAddNewLine() } else if (shiftKey) { // direction up direction = CELL_SELECTION_DIRECTION.UP this[INSTANCE_METHODS.STOP_EDITING_CELL]() } else if (ctrlKey) { // stop editing and stay in current cell this[INSTANCE_METHODS.STOP_EDITING_CELL]() } else { // direction down direction = CELL_SELECTION_DIRECTION.DOWN this[INSTANCE_METHODS.STOP_EDITING_CELL]() } if (direction) { this.clearCellSelectionNormalEndCell() this.selectCellByDirection({ direction, }) } event.preventDefault() break } case KEY_CODES.SPACE: { if (!isCellEditing) { // start editing and enter a space this[INSTANCE_METHODS.START_EDITING_CELL]({ rowKey, colKey, defaultValue: ' ', }) event.preventDefault() } break } case KEY_CODES.BACK_SPACE: { if (!isCellEditing) { // start editing and clear value this[INSTANCE_METHODS.START_EDITING_CELL]({ rowKey, colKey, defaultValue: '', }) event.preventDefault() } break } case KEY_CODES.DELETE: { if (!isCellEditing) { // delete cell selection range value this.deleteCellSelectionRangeValue() event.preventDefault() } break } case KEY_CODES.F2: { if (!isCellEditing) { if (currentColumn.edit) { // start editing cell and don't allow stop eidting by direction key this.enableStopEditing = false this[INSTANCE_METHODS.START_EDITING_CELL]({ rowKey, colKey, }) } event.preventDefault() } break } default: { // enter text directly if (isInputKeyCode(event)) { this[INSTANCE_METHODS.START_EDITING_CELL]({ rowKey, colKey, defaultValue: '', }) } break } } } }, // select cell by direction selectCellByDirection({ direction }) { const { colgroups, allRowKeys, cellSelectionData } = this const { rowKey, colKey } = cellSelectionData.currentCell const columnIndex = colgroups.findIndex((x) => x.key === colKey) const rowIndex = allRowKeys.indexOf(rowKey) if (direction === CELL_SELECTION_DIRECTION.LEFT) { if (columnIndex > 0) { const nextColumn = colgroups[columnIndex - 1] this.cellSelectionData.currentCell.colKey = nextColumn.key this.columnToVisible(nextColumn) } } else if (direction === CELL_SELECTION_DIRECTION.RIGHT) { if (columnIndex < colgroups.length - 1) { const nextColumn = colgroups[columnIndex + 1] this.cellSelectionData.currentCell.colKey = nextColumn.key this.columnToVisible(nextColumn) } } else if (direction === CELL_SELECTION_DIRECTION.UP) { if (rowIndex > 0) { const nextRowKey = allRowKeys[rowIndex - 1] this.rowToVisible(KEY_CODES.ARROW_UP, nextRowKey) } } else if (direction === CELL_SELECTION_DIRECTION.DOWN) { if (rowIndex < allRowKeys.length - 1) { const nextRowKey = allRowKeys[rowIndex + 1] this.rowToVisible(KEY_CODES.ARROW_DOWN, nextRowKey) } } }, /* * @columnToVisible * @desc column to visible * @param {object} nextColumn - next column */ columnToVisible(nextColumn) { const { hasXScrollBar, colgroups } = this // if (!nextColumn || nextColumn) return if (!hasXScrollBar) {