UNPKG

zent

Version:

一套前端设计语言和基于React的实现

498 lines (443 loc) 13.4 kB
/* eslint-disable no-lonely-if */ import React, { Component, PureComponent } from 'react'; import ReactDOM from 'react-dom'; import Loading from 'loading'; import PropTypes from 'prop-types'; import isBrowser from 'utils/isBrowser'; import throttle from 'lodash/throttle'; import intersection from 'lodash/intersection'; import uniq from 'lodash/uniq'; import uniqBy from 'lodash/uniqBy'; import pullAll from 'lodash/pullAll'; import pullAllBy from 'lodash/pullAllBy'; import Head from './modules/Head'; import Body from './modules/Body'; import Foot from './modules/Foot'; import helper from './helper'; const { func, bool, string, array, oneOf, object } = PropTypes; export default class Table extends (PureComponent || Component) { static propTypes = { className: string, prefix: string, columns: array, datasets: array, onChange: func, sortBy: string, sortType: oneOf(['desc', 'asc']), pageInfo: object, rowKey: string, loading: bool, autoScroll: bool, autoStick: bool, selection: object, expandation: object, batchComponentsAutoFixed: bool, batchComponents: array }; static defaultProps = { prefix: 'zent', pageSize: 10, className: '', datasets: [], columns: [], emptyLabel: '没有更多数据了', rowKey: 'id', sortType: 'desc', loading: false, autoScroll: false, autoStick: false, selection: null, batchComponentsAutoFixed: true, batchComponents: null }; constructor(props) { super(props); this.state = { current: props.pageInfo ? props.pageInfo.current : 1, batchComponentsFixed: false, placeHolderHeight: false, fixStyle: {} }; this.tableRect = null; this.relativeTop = 0; } // 一个global的selectedRowKeys用于保存所有选中的选项 selectedRowKeys = []; selectedRows = []; componentWillReceiveProps(nextProps) { const toggleListener = helper.toggleEventListener(this.props, nextProps); toggleListener && this[toggleListener](nextProps); this.setState({ current: nextProps.pageInfo ? nextProps.pageInfo.current : 1 }); } componentDidMount() { this.addEventListener(this.props); } componentWillUnmount() { this.removeEventListener(this.props); } addEventListener(props) { if (props.batchComponentsAutoFixed) { const { batchComponents } = props; this.setRectParam(); if (batchComponents && batchComponents.length > 0) { this.throttleSetBatchComponents = throttle( () => { this.setRectParam(); this.toggleBatchComponents(); }, 100, { leading: true } ); window.addEventListener( 'scroll', this.throttleSetBatchComponents, true ); window.addEventListener( 'resize', this.throttleSetBatchComponents, true ); } } } removeEventListener(props) { if (props.batchComponentsAutoFixed) { window.removeEventListener( 'scroll', this.throttleSetBatchComponents, true ); window.removeEventListener( 'resize', this.throttleSetBatchComponents, true ); } } setRectParam() { this.tableRectTop = ReactDOM.findDOMNode(this).getBoundingClientRect().top; this.tableRectHeight = ReactDOM.findDOMNode( this ).getBoundingClientRect().height; this.relativeTop = this.tableRectTop - document.documentElement.getBoundingClientRect().top; } toggleBatchComponents() { const needFixedBatchComps = helper.needFixBatchComps( this.isTableInView(), this.isFootInView(), this.props.selection.selectedRowKeys.length > 0, this.state.batchComponentsFixed ); if (typeof needFixedBatchComps === 'boolean') { this.setState({ batchComponentsFixed: needFixedBatchComps }); } } // 对外部传进来的onChange进行封装 wrapPropsOnChange(conf) { if (typeof this.props.onChange !== 'function') { throw new Error('请传入一个onChange方法'); } this.props.onChange(conf); } onChange = conf => { this.setState(conf); this.wrapPropsOnChange(conf); }; onSort = conf => { // 排序的时候也要触发 this.wrapPropsOnChange(conf); }; onPageChange = current => { this.wrapPropsOnChange({ current }); if (this.props.autoScroll) { this.scrollToTop(400); } }; /** * 设置内部属性,cached选中结果 */ setSelection() { let { selection } = this.props; this.selectedRowKeys = selection.selectedRowKeys.slice(0); // copy 一份数组 this.selectedRows = this.getSelectedRowsByKeys(this.selectedRowKeys); } /* * Head上的选中会全选所有的行 * @param isSelect {Boolean} 表示是否全选 */ onSelectAllRows = isSelect => { let rowKeysCurrentPage = []; let rowsCurrentPage = []; let { rowKey, datasets, selection, getRowConf = () => { return { canSelect: true }; } } = this.props; this.setSelection(); let allRowKeys = this.selectedRowKeys; let allRows = this.selectedRows; // 找出所有canSelect为true的row for (let i = 0, len = datasets.length; i < len; i++) { let { canSelect = true } = getRowConf(datasets[i], i); if (canSelect) { rowKeysCurrentPage.push(datasets[i][rowKey]); rowsCurrentPage.push(datasets[i]); } } if (isSelect) { if (this.props.selection.needCrossPage) { allRowKeys = uniq(allRowKeys.concat(rowKeysCurrentPage)); allRows = uniqBy(allRows.concat(rowsCurrentPage), rowKey); } else { allRowKeys = rowKeysCurrentPage; allRows = rowsCurrentPage; } } else { if (this.props.selection.needCrossPage) { allRowKeys = pullAll(allRowKeys, rowKeysCurrentPage); allRows = pullAllBy(allRows, rowsCurrentPage, rowKey); } else { allRowKeys = []; allRows = []; } } this.selectedRowKeys = allRowKeys; this.selectedRows = allRows; selection.onSelect(this.selectedRowKeys, this.selectedRows, null); }; /** * 选了一行 * @param rowKey {String} 某一行的key * @param isSelect {Boolean} 是否被选中 */ onSelectOneRow = (rowKey, isSelect) => { let { selection } = this.props; this.setSelection(); let index = this.selectedRowKeys.indexOf(rowKey); let isSingleSelection = selection.isSingleSelection || false; if (isSingleSelection) { // radio的isSelect永远是true,所以一旦选择了,则不能取消 if (isSelect) { this.selectedRowKeys = [rowKey]; } else { this.selectedRowKeys = []; } } else if (isSelect && index === -1) { this.selectedRowKeys.push(rowKey); } else if (index !== -1) { this.selectedRowKeys.splice(index, 1); } if (!selection.needCrossPage) { this.selectedRowKeys = intersection( this.selectedRowKeys, this.props.datasets.map(item => item[this.props.rowKey]) ); } let selectedRows = this.getSelectedRowsByKeys(this.selectedRowKeys); let currentRow = isSelect ? this.getCurrentRow(rowKey) : null; selection.onSelect(this.selectedRowKeys, selectedRows, currentRow); }; getCurrentRow(key) { let currentRow; let self = this; if (key) { this.props.datasets.forEach(item => { if (item[self.props.rowKey] === key) { currentRow = item; } }); } return currentRow; } isTableInView() { const tableY = this.tableRectTop - document.documentElement.getBoundingClientRect().top; return ( tableY + this.tableRectHeight > window.pageYOffset && tableY <= window.pageYOffset + window.innerHeight ); } isFootInView() { const footRect = ReactDOM.findDOMNode(this.foot).getBoundingClientRect(); const footY = footRect.top - document.documentElement.getBoundingClientRect().top; return ( footY + footRect.height > window.pageYOffset && footY <= window.pageYOffset + window.innerHeight ); } /** * 根据选择的keys拼装一个选好的列 * @param rowKeys Array 一个keys的数组 * @return rows Array 一个每行的数据的数组 */ getSelectedRowsByKeys(rowKeys) { let rows = []; let self = this; // 之前缓存的rows和本页的总datasets整个作为搜索的区间 let allRows = uniqBy( this.selectedRows.concat(this.props.datasets), this.props.rowKey ); allRows.forEach(item => { if (rowKeys.indexOf(item[self.props.rowKey]) >= 0) { rows.push(item); } }); return rows; } scrollToTop(scrollDuration) { if (!isBrowser) return; const scrollHeight = window.scrollY; const scrollStep = Math.PI / (scrollDuration / 15); const cosParameter = scrollHeight / 2; let scrollCount = 0; let scrollMargin; let scrollInterval = setInterval(() => { if (window.scrollY > this.relativeTop) { scrollCount += 1; scrollMargin = cosParameter - cosParameter * Math.cos(scrollCount * scrollStep); window.scrollTo(0, scrollHeight - scrollMargin); } else { clearInterval(scrollInterval); } }, 16); } render() { let { selection, prefix, columns, className, sortBy, autoStick, sortType, datasets, rowKey, pageInfo, emptyLabel, getRowConf = () => { return { canSelect: true, rowClass: '' }; }, expandation = null, batchComponents = null } = this.props; let needSelect = selection !== null; let isSingleSelection; if (selection) { isSingleSelection = selection.isSingleSelection || false; } let selectedRowKeys = []; let canSelectAll = false; let isSelectAll = false; let isSelectPart = false; let canRowSelect = false; let needExpand = false; let isExpanded; let expandRender; if (expandation) { needExpand = true; isExpanded = expandation.isExpanded; expandRender = expandation.expandRender; } if (needSelect) { const canSelectRowKeysArr = []; datasets.forEach((item, index) => { let { canSelect = true } = getRowConf(item, index); if (canSelect) { canSelectRowKeysArr.push(item[rowKey]); } }); selectedRowKeys = selection.selectedRowKeys || []; canSelectAll = canSelectRowKeysArr.length > 0; canRowSelect = selection.canRowSelect; isSelectAll = canSelectRowKeysArr.length > 0 && helper.isSelectAll(selectedRowKeys, canSelectRowKeysArr); isSelectPart = canSelectRowKeysArr.length > 0 && !isSelectAll && helper.isSelectPart(selectedRowKeys, canSelectRowKeysArr); } return ( <div className={`${prefix}-table-container`}> <Loading show={this.props.loading} static> {columns && ( <div className={`${prefix}-table ${className}`}> {this.state.placeHolderHeight && ( <div className="thead place-holder"> <div className="tr">{this.cloneHeaderContent()}</div> </div> )} <Head ref={c => (this.head = c)} columns={columns} sortBy={sortBy} sortType={sortType} onSort={this.onSort} selection={{ needSelect, onSelectAll: this.onSelectAllRows, isSingleSelection, canSelectAll, isSelectAll, isSelectPart }} needExpand={needExpand} autoStick={autoStick} style={this.state.fixStyle} /> <Body datasets={datasets} columns={columns} emptyLabel={emptyLabel} rowKey={rowKey} getRowConf={getRowConf} selection={{ needSelect, selectedRowKeys, isSingleSelection, onSelect: this.onSelectOneRow, canRowSelect }} needExpand={needExpand} isExpanded={isExpanded} expandRender={expandRender} /> <Foot ref={c => (this.foot = c)} batchComponents={batchComponents} pageInfo={pageInfo} batchComponentsFixed={this.state.batchComponentsFixed} selection={{ needSelect, isSingleSelection, onSelectAll: this.onSelectAllRows, selectedRows: this.getSelectedRowsByKeys(selectedRowKeys), isSelectAll, isSelectPart }} current={this.state.current} onPageChange={this.onPageChange} /> </div> )} </Loading> </div> ); } }