UNPKG

yk-smart-ui-test

Version:

A Component Library for Vue.js.

546 lines (507 loc) 20 kB
import { arrayFindIndex } from 'element-ui/src/utils/util'; import { getCell, getColumnByCell, getRowIdentity } from './util'; import { getStyle, hasClass, removeClass, addClass } from 'element-ui/src/utils/dom'; import YkCheckbox from 'element-ui/packages/checkbox'; import YkTooltip from 'element-ui/packages/tooltip'; import YkInput from 'element-ui/packages/input'; import debounce from 'throttle-debounce/debounce'; import LayoutObserver from './layout-observer'; import { mapStates } from './store/helper'; import { defaultRenderCell } from './config'; export default { name: 'ElTableBody', mixins: [LayoutObserver], components: { YkCheckbox, YkTooltip, YkInput }, props: { store: { required: true }, stripe: Boolean, context: {}, rowClassName: [String, Function], rowStyle: [Object, Function], fixed: String, highlight: Boolean }, render(h) { const data = this.data || []; return ( <table class="yk-table__body" cellspacing="0" cellpadding="0" border="0"> <colgroup> { this.columns.map(column => <col name={column.id} key={column.id} />) } </colgroup> <tbody> { data.reduce((acc, row) => { return acc.concat(this.wrappedRowRender(row, acc.length)); }, []) } <div class='labelText' style='color:black; line-height:1.2; white-space: nowrap; position:fixed;top:0px; left:0px; display:block;z-index:9999;visibility: hidden;' ></div> <yk-tooltip popper-class="table-tooltip" effect={this.table.tooltipEffect} placement="top" ref="tooltip" content={this.tooltipContent}></yk-tooltip> </tbody> </table> ); }, computed: { table() { return this.$parent; }, ...mapStates({ data: 'data', columns: 'columns', treeIndent: 'indent', leftFixedLeafCount: 'fixedLeafColumnsLength', rightFixedLeafCount: 'rightFixedLeafColumnsLength', columnsCount: states => states.columns.length, leftFixedCount: states => states.fixedColumns.length, rightFixedCount: states => states.rightFixedColumns.length, hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand') }), firstDefaultColumnIndex() { return arrayFindIndex(this.columns, ({ type }) => type === 'default'); } }, watch: { // don't trigger getter of currentRow in getCellClass. see https://jsfiddle.net/oe2b4hqt/ // update DOM manually. see https://github.com/ElemeFE/element/pull/13954/files#diff-9b450c00d0a9dec0ffad5a3176972e40 'store.states.hoverRow'(newVal, oldVal) { if (!this.store.states.isComplex || this.$isServer) return; let raf = window.requestAnimationFrame; if (!raf) { raf = (fn) => setTimeout(fn, 16); } raf(() => { const rows = this.$el.querySelectorAll('.yk-table__row'); const oldRow = rows[oldVal]; const newRow = rows[newVal]; if (oldRow) { removeClass(oldRow, 'hover-row'); } if (newRow) { addClass(newRow, 'hover-row'); } }); } }, data() { return { tooltipContent: '', userInput: {} }; }, created() { this.activateTooltip = debounce(50, tooltip => tooltip.handleShowPopper()); for (let i = 0; i < this.data.length; i++) { this.$set(this.userInput, i, {}); } }, methods: { getKeyOfRow(row, index) { const rowKey = this.table.rowKey; if (rowKey) { return getRowIdentity(row, rowKey); } return index; }, isColumnHidden(index) { if (this.fixed === true || this.fixed === 'left') { return index >= this.leftFixedLeafCount; } else if (this.fixed === 'right') { return index < this.columnsCount - this.rightFixedLeafCount; } else { return (index < this.leftFixedLeafCount) || (index >= this.columnsCount - this.rightFixedLeafCount); } }, getSpan(row, column, rowIndex, columnIndex) { let rowspan = 1; let colspan = 1; const fn = this.table.spanMethod; if (typeof fn === 'function') { const result = fn({ row, column, rowIndex, columnIndex }); if (Array.isArray(result)) { rowspan = result[0]; colspan = result[1]; } else if (typeof result === 'object') { rowspan = result.rowspan; colspan = result.colspan; } } return { rowspan, colspan }; }, getRowStyle(row, rowIndex) { const rowStyle = this.table.rowStyle; if (typeof rowStyle === 'function') { return rowStyle.call(null, { row, rowIndex }); } return rowStyle || null; }, getRowClass(row, rowIndex) { const classes = ['yk-table__row']; if (this.table.highlightCurrentRow && row === this.store.states.currentRow) { classes.push('current-row'); } if (this.stripe && rowIndex % 2 === 1) { classes.push('yk-table__row--striped'); } const rowClassName = this.table.rowClassName; if (typeof rowClassName === 'string') { classes.push(rowClassName); } else if (typeof rowClassName === 'function') { classes.push(rowClassName.call(null, { row, rowIndex })); } if (this.store.states.expandRows.indexOf(row) > -1) { classes.push('expanded'); } return classes; }, getCellStyle(rowIndex, columnIndex, row, column) { const cellStyle = this.table.cellStyle; if (typeof cellStyle === 'function') { return cellStyle.call(null, { rowIndex, columnIndex, row, column }); } return cellStyle; }, getCellClass(rowIndex, columnIndex, row, column) { const classes = [column.id, column.align, column.className]; if (this.isColumnHidden(columnIndex)) { classes.push('is-hidden'); } const cellClassName = this.table.cellClassName; if (typeof cellClassName === 'string') { classes.push(cellClassName); } else if (typeof cellClassName === 'function') { classes.push(cellClassName.call(null, { rowIndex, columnIndex, row, column })); } return classes.join(' '); }, getColspanRealWidth(columns, colspan, index) { if (colspan < 1) { return columns[index].realWidth; } const widthArr = columns.map(({ realWidth }) => realWidth).slice(index, index + colspan); return widthArr.reduce((acc, width) => acc + width, -1); }, handleCellMouseEnter(event, row) { const table = this.table; const cell = getCell(event); if (cell) { const column = getColumnByCell(table, cell); const hoverState = table.hoverState = { cell, column, row }; table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event); } // 判断是否text-overflow, 如果是就显示tooltip const cellChild = event.target.querySelector('.cell'); if (!(hasClass(cellChild, 'yk-tooltip') && cellChild.childNodes.length)) { return; } // use range width instead of scrollWidth to determine whether the text is overflowing // to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3 const range = document.createRange(); range.setStart(cellChild, 0); range.setEnd(cellChild, cellChild.childNodes.length); let labTextObj = document.querySelector('.labelText'); labTextObj.style.fontSize = window.getComputedStyle(cellChild).fontSize; let paddingRightText = window.getComputedStyle(cellChild).paddingRight; let paddingLeftText = window.getComputedStyle(cellChild).paddingLeft; labTextObj.style.paddingLeft = paddingLeftText.substring(0, paddingLeftText.length - 2) * 2 + 'px'; labTextObj.style.paddingRight = paddingRightText.substring(0, paddingRightText.length - 2) * 2 + 'px'; labTextObj.innerHTML = (cell.innerText || cell.textContent); if ((cellChild.offsetWidth) * 2 <= labTextObj.offsetWidth + 1 && cellChild.children.length === 0) { const tooltip = this.$refs.tooltip; // TODO 会引起整个 Table 的重新渲染,需要优化 this.tooltipContent = cell.innerText || cell.textContent; tooltip.referenceElm = cell; tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none'); tooltip.doDestroy(); tooltip.setExpectedState(true); this.activateTooltip(tooltip); } }, // 折行显示tooltip tooltipContentText(cellChild, text) { let domFontSzie = parseInt(getStyle(cellChild, 'fontSize'), 10) || 12; let domWidth = cellChild.clientWidth - ((parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) + (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)); let sub = parseInt(domWidth / domFontSzie, 10) + 1; let textLenth = cellChild.textContent.length; let retText = ''; for (var i = 0; i < textLenth; i += sub) { retText += text(i, sub) + '<br/>'; } return retText; }, handleCellMouseLeave(event) { const tooltip = this.$refs.tooltip; if (tooltip) { tooltip.setExpectedState(false); tooltip.handleClosePopper(); } const cell = getCell(event); if (!cell) return; const oldHoverState = this.table.hoverState || {}; this.table.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event); }, handleMouseEnter: debounce(30, function(index) { this.store.commit('setHoverRow', index); }), handleMouseLeave: debounce(30, function() { this.store.commit('setHoverRow', null); }), handleContextMenu(event, row) { this.handleEvent(event, row, 'contextmenu'); }, handleDoubleClick(event, row) { this.handleEvent(event, row, 'dblclick'); }, handleClick(event, row) { this.store.commit('setCurrentRow', row); this.handleEvent(event, row, 'click'); }, handleEvent(event, row, name) { const table = this.table; const cell = getCell(event); let column; if (cell) { column = getColumnByCell(table, cell); if (column) { table.$emit(`cell-${name}`, row, column, cell, event); } } table.$emit(`row-${name}`, row, column, event); }, rowRender(row, $index, treeRowData) { const { treeIndent, columns, firstDefaultColumnIndex } = this; const columnsHidden = columns.map((column, index) => this.isColumnHidden(index)); const rowClasses = this.getRowClass(row, $index); let display = true; if (treeRowData) { rowClasses.push('yk-table__row--level-' + treeRowData.level); display = treeRowData.display; } // 指令 v-show 会覆盖 row-style 中 display // 使用 :style 代替 v-show https://github.com/ElemeFE/element/issues/16995 let displayStyle = display ? null : { display: 'none' }; return (<tr style={[displayStyle, this.getRowStyle(row, $index)]} class={rowClasses} key={this.getKeyOfRow(row, $index)} on-dblclick={($event) => this.handleDoubleClick($event, row)} on-click={($event) => this.handleClick($event, row)} on-contextmenu={($event) => this.handleContextMenu($event, row)} on-mouseenter={_ => this.handleMouseEnter($index)} on-mouseleave={this.handleMouseLeave}> { columns.map((column, cellIndex) => { const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex); if (!rowspan || !colspan) { return null; } const columnData = { ...column }; columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex); const data = { store: this.store, _self: this.context || this.table.$vnode.context, column: columnData, row, $index }; if (cellIndex === firstDefaultColumnIndex && treeRowData) { data.treeNode = { indent: treeRowData.level * treeIndent, level: treeRowData.level }; if (typeof treeRowData.expanded === 'boolean') { data.treeNode.expanded = treeRowData.expanded; // 表明是懒加载 if ('loading' in treeRowData) { data.treeNode.loading = treeRowData.loading; } if ('noLazyChildren' in treeRowData) { data.treeNode.noLazyChildren = treeRowData.noLazyChildren; } } } // 如果可编辑 if (column.textEdit) { return ( <td style={this.getCellStyle($index, cellIndex, row, column)} class={this.getCellClass($index, cellIndex, row, column)} rowspan={rowspan} colspan={colspan} on-mouseenter={($event) => this.handleCellMouseEnter($event, row)} on-mouseleave={this.handleCellMouseLeave}> <yk-input class="yk-table__editor is-in-table" value={ (this.userInput[$index][cellIndex] !== null && this.userInput[$index][cellIndex] !== undefined) ? this.userInput[$index][cellIndex] : this.textEditValue(this.$createElement, data) } onInput={ val => this.handleEditInput(val, $index, cellIndex) } onBlur={ ($event) => this.handleEditBlur($event, row, $index, cellIndex) } placeholder="请输入内容" /> </td> ); } return ( <td style={this.getCellStyle($index, cellIndex, row, column)} class={this.getCellClass($index, cellIndex, row, column)} rowspan={rowspan} colspan={colspan} on-mouseenter={($event) => this.handleCellMouseEnter($event, row)} on-mouseleave={this.handleCellMouseLeave}> { column.renderCell.call( this._renderProxy, this.$createElement, data, columnsHidden[cellIndex] ) } </td> ); }) } </tr>); }, textEditValue(h, data) { return defaultRenderCell(h, data); }, handleEditInput(value, rowIndex, colIndex) { this.$set(this.userInput[rowIndex], colIndex, value); }, handleEditBlur(event, row, rowIndex, colIndex) { const table = this.table; const cell = getCell(event); if (cell) { const column = getColumnByCell(table, cell); const hoverState = table.hoverState = { cell, column, row }; table.$emit('cell-edit-blur', hoverState.row, hoverState.column, this.userInput[rowIndex][colIndex]); // this.userInput[rowIndex][colIndex] = null; } }, wrappedRowRender(row, $index) { const store = this.store; const { isRowExpanded, assertRowKey } = store; const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } = store.states; if (this.hasExpandColumn && isRowExpanded(row)) { const renderExpanded = this.table.renderExpanded; const tr = this.rowRender(row, $index); if (!renderExpanded) { console.error('[Element Error]renderExpanded is required.'); return tr; } // 使用二维数组,避免修改 $index return [[ tr, <tr key={'expanded-row__' + tr.key}> <td colspan={this.columnsCount} class="yk-table__expanded-cell"> {renderExpanded(this.$createElement, { row, $index, store: this.store })} </td> </tr>]]; } else if (Object.keys(treeData).length) { assertRowKey(); // TreeTable 时,rowKey 必须由用户设定,不使用 getKeyOfRow 计算 // 在调用 rowRender 函数时,仍然会计算 rowKey,不太好的操作 const key = getRowIdentity(row, rowKey); let cur = treeData[key]; let treeRowData = null; if (cur) { treeRowData = { expanded: cur.expanded, level: cur.level, display: true }; if (typeof cur.lazy === 'boolean') { if (typeof cur.loaded === 'boolean' && cur.loaded) { treeRowData.noLazyChildren = !(cur.children && cur.children.length); } treeRowData.loading = cur.loading; } } const tmp = [this.rowRender(row, $index, treeRowData)]; // 渲染嵌套数据 if (cur) { // currentRow 记录的是 index,所以还需主动增加 TreeTable 的 index let i = 0; const traverse = (children, parent) => { if (!(children && children.length && parent)) return; children.forEach(node => { // 父节点的 display 状态影响子节点的显示状态 const innerTreeRowData = { display: parent.display && parent.expanded, level: parent.level + 1 }; const childKey = getRowIdentity(node, rowKey); if (childKey === undefined || childKey === null) { throw new Error('for nested data item, row-key is required.'); } cur = { ...treeData[childKey] }; // 对于当前节点,分成有无子节点两种情况。 // 如果包含子节点的,设置 expanded 属性。 // 对于它子节点的 display 属性由它本身的 expanded 与 display 共同决定。 if (cur) { innerTreeRowData.expanded = cur.expanded; // 懒加载的某些节点,level 未知 cur.level = cur.level || innerTreeRowData.level; cur.display = !!(cur.expanded && innerTreeRowData.display); if (typeof cur.lazy === 'boolean') { if (typeof cur.loaded === 'boolean' && cur.loaded) { innerTreeRowData.noLazyChildren = !(cur.children && cur.children.length); } innerTreeRowData.loading = cur.loading; } } i++; tmp.push(this.rowRender(node, $index + i, innerTreeRowData)); if (cur) { const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName]; traverse(nodes, cur); } }); }; // 对于 root 节点,display 一定为 true cur.display = true; const nodes = lazyTreeNodeMap[key] || row[childrenColumnName]; traverse(nodes, cur); } return tmp; } else { return this.rowRender(row, $index); } } } };