高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
封装的一个table组件的高阶组件。
import React, { Component } from 'react';
import { Checkbox } from 'antd';
import './style.less';
import {
dataFormat,
clickTitleSorter,
tableSorterChange,
getFilteredArray,
sorterRule,
isNaNOrNull
} from '@/utils';
import { makePy } from '@/utils/wordToPinyin';
const CheckboxGroup = Checkbox.Group;
/**
* 高阶组件 表格
* 使用说明:
* 必传参数:columns
* 1、排序判断字段:
* sorterType
* 'zh': 中文 'en': 中文 'num': 中文 无: 不排序
* 2、筛选判断字段:
* isFilter Boolean
* 可选参数
* getSelectedRows: 多选框
*/
export default WrappedComponent =>
class TableHoc extends Component {
constructor(props) {
super(props);
this.state = {
columns: [],
dataSource: [],
loading: false,
currentPage: 1,
rowClassName: '',
selectedArr: [],
selectedRowKeys: [],
expandedRowKeys: []
};
}
componentDidMount() {
const { columns, tableData = [], pageSize = 10, loading = false } = this.props;
this.setColumns(columns, tableData, pageSize);
this.setState({loading, dataSource: this.setDataSource(tableData, columns)})
}
componentWillReceiveProps(nextProps) {
const {
columns,
tableData = [],
loading = false,
currentPage = 1,
rowClassName,
pageSize = 10
} = nextProps;
const dataSource = this.setDataSource(tableData, columns);
// 编辑和删除不改变页码
// 编辑
if (currentPage === 0) {
this.setState({ loading, dataSource });
return;
}
// 删除
if (currentPage === -1) {
// 删除某一页(>1)的唯一一条数据,需设置页码-1
if (
tableData.length % pageSize === 0 &&
this.state.currentPage - 1 > 0
) {
this.setState({
currentPage: this.state.currentPage - 1,
loading,
dataSource
});
return;
}
this.setState({ loading, dataSource });
return;
}
if (currentPage > 0) {
this.setState({
loading,
dataSource
});
if (currentPage !== this.props.currentPage) {
this.setState({currentPage});
}
if (rowClassName !== this.props.rowClassName) {
this.setState({rowClassName})
}
if (this.props.tableData.length !== tableData.length) {
this.setColumns(columns, tableData, pageSize);
}
}
}
setColumns(columns, tableData, pageSize) {
this.setState({
columns: columns.map((e, k) => {
const dataIndex = e.dataIndex;
const align = e.align ? e.align : 'center';
const render = e.render ? e.render : t => dataFormat(t);
const className = e.className ? e.className : '';
if (e.title === '序号') {
return {
title: e.title,
dataIndex,
align,
width: e.width,
className,
render: (t, r, idx) =>
dataFormat(pageSize * (this.state.currentPage - 1) + 1 + idx)
};
}
// 情形1: 排序+筛选
if (e.sorterType && e.isFilter) {
return {
title: <a onClick={() => this.clickTitle(k)}>{e.title}</a>,
dataIndex,
align,
width: e.width,
className,
isCheckAll: true,
statArr: getFilteredArray(tableData, e.dataIndex),
checkedList: getFilteredArray(tableData, e.dataIndex),
oldCheckedList: [],
filterDropdownVisible: false,
onFilterDropdownVisibleChange: visible =>
this.onFilterDropdownVisibleChange(visible, k),
filterDropdown: () => this.renderFilterDropdown(k),
filterIcon: () => this.renderFilterIcon(k),
sorter: this.setSorter(e),
render
};
}
// 情形2: 排序
if (e.sorterType) {
return {
title: <a onClick={() => this.clickTitle(k)}>{e.title}</a>,
dataIndex,
align,
width: e.width,
className,
sortOrder: e.sortOrder,
sorter: this.setSorter(e),
render
};
}
// 情形3: 筛选
if (e.isFilter) {
return {
title: e.title,
dataIndex,
align,
width: e.width,
className,
isCheckAll: true,
statArr: getFilteredArray(tableData, e.dataIndex),
checkedList: getFilteredArray(tableData, e.dataIndex),
oldCheckedList: [],
filterDropdownVisible: false,
onFilterDropdownVisibleChange: visible =>
this.onFilterDropdownVisibleChange(visible, k),
filterDropdown: () => this.renderFilterDropdown(k),
filterIcon: () => this.renderFilterIcon(k),
render
};
}
// 情形4: 展示
return {
title: e.title,
dataIndex: e.dataIndex,
align: e.align ? e.align : 'center',
width: e.width,
className: e.className,
render
};
})
});
}
setDataSource(tableData, columns) {
let dataSource = [];
if (tableData.length) {
dataSource = tableData.map((v, k) => {
const obj = v;
obj.key = k;
columns.forEach(e => {
// 中文排序需将中文转换为对应的拼音首字母
if (e.sorterType === 'zh') {
if (isNaNOrNull(obj[e.dataIndex]) === '-') {
obj[`${e.dataIndex}En`] = '';
} else if (obj[e.dataIndex].label === undefined) {
obj[`${e.dataIndex}En`] = obj[e.dataIndex]
? makePy(obj[e.dataIndex])
: '';
} else {
obj[`${e.dataIndex}En`] = obj[e.dataIndex].label
? makePy(obj[e.dataIndex].label)
: '';
}
}
});
return obj;
});
}
return dataSource;
}
setSorter(e) {
switch (e.sorterType) {
case 'en':
return (a, b) => a[e.dataIndex].localeCompare(b[e.dataIndex]);
case 'zh':
return (a, b) =>
a[`${e.dataIndex}En`].localeCompare(b[`${e.dataIndex}En`]);
default:
if (e.sorter) {
return e.sorter;
}
return (a, b) => sorterRule(a[e.dataIndex], b[e.dataIndex]);
}
}
/**
* 点击表头排序
* @param {number} index
*/
clickTitle(index) {
const { columns, dataSource } = this.state;
this.setState({
columns: clickTitleSorter(index, [...columns], dataSource)
});
}
/**
* 渲染过滤多选框
*/
renderFilterDropdown(k) {
const { columns } = this.state;
const statArr = columns[k].statArr;
const isCheckAll = columns[k].isCheckAll;
const checkedList = columns[k].checkedList;
return (
<div className="fliter-dropdown">
<div className="dropdown-checkbox">
<Checkbox
checked={isCheckAll}
onChange={e => this.onCheckAllChange(e, k)}
>
全部
</Checkbox>
<CheckboxGroup
options={statArr}
value={checkedList}
onChange={checkedList => this.onCheckedListChange(checkedList, k)}
/>
</div>
<div className="dropdown-btns">
<a
className="btn btn-confirm"
onClick={() => this.onClickConfirm(k)}
>
确定
</a>
<a className="btn btn-cancel" onClick={() => this.onClickCancle(k)}>
取消
</a>
</div>
</div>
);
}
/**
* 点击全部回调
* @param {event} e
*/
onCheckAllChange(e, k) {
const { columns } = this.state;
const statArr = columns[k].statArr;
columns[k].checkedList = e.target.checked ? statArr : [];
columns[k].isCheckAll = e.target.checked;
this.setState({ columns });
}
/**
* 选中单项回调
* @param {array} checkedList
*/
onCheckedListChange(checkedList, k) {
const { columns } = this.state;
columns[k].checkedList = checkedList;
columns[k].isCheckAll = checkedList.length === columns[k].statArr.length;
this.setState({ columns });
}
/**
* 筛选下拉框状态回调
* @param {boolean} visible
*/
onFilterDropdownVisibleChange(visible, index) {
const { columns } = this.state;
const checkedList = columns[index].checkedList;
const oldCheckedList = columns[index].oldCheckedList;
const statArr = columns[index].statArr;
columns[index].filterDropdownVisible = visible;
if (visible) {
columns[index].oldCheckedList = checkedList;
} else {
// 点击其他地方,弹窗消失
// 回归到上次点击确定的多选状态
columns[index].checkedList = oldCheckedList;
columns[index].isCheckAll = oldCheckedList.length === statArr.length;
}
this.setState({ columns });
}
/**
* 过滤弹框点击确定
* @param {number} index
*/
onClickConfirm(index) {
const { columns } = this.state;
const { tableData } = this.props;
const checkedList = columns[index].checkedList;
const dataIndex = columns[index].dataIndex;
if (!checkedList.length) {
return;
}
const dataSource = [];
tableData.forEach(e => {
checkedList.forEach(v => {
if (e[dataIndex].label === v) {
dataSource.push(e);
} else if (e[dataIndex] === v) {
dataSource.push(e);
}
});
});
columns[index].filterDropdownVisible = false;
this.setState({ columns, dataSource, currentPage: 1 });
}
/**
* 过滤弹框点击取消
* @param {number} index
*/
onClickCancle(index) {
const { columns } = this.state;
const oldCheckedList = columns[index].oldCheckedList;
const statArr = columns[index].statArr;
columns[index].filterDropdownVisible = false;
// 回归到上次点击确定的多选状态
columns[index].checkedList = oldCheckedList;
columns[index].isCheckAll = oldCheckedList.length === statArr.length;
this.setState({ columns });
}
/**
* 渲染过滤图标样式
* @param {number} index
*/
renderFilterIcon(index) {
const { columns } = this.state;
const checkedList = columns[index].checkedList;
const statArr = columns[index].statArr;
const isFiltered = checkedList.length !== statArr.length;
return (
<i
className={
isFiltered ? 'filter-result i-hover' : 'filter-normal i-hover'
}
/>
);
}
// 多选框
rowSelection() {
const { selectedRowKeys, dataSource } = this.state;
const len =dataSource.length;
return {
fixed: true,
selectedRowKeys,
hideDefaultSelections: true,
selections: [{
key: 'all-data',
text: '全选',
onSelect: () => {
this.setState({
selectedRowKeys: [...Array(len).keys()],
}, () => {
this.props.getSelectedRows(dataSource);
});
},
},
{
key: 'no-data',
text: '取消全选',
onSelect: () => {
this.setState({
selectedRowKeys: [],
}, () => {
this.props.getSelectedRows([]);
});
},
}],
onChange: (selectedRowKeys, selectedRows) => {
this.setState({
selectedRowKeys
}, () => {
this.props.getSelectedRows(selectedRows);
});
},
};
}
/**
* 点击排序上下图标回调
* @param {object} sorter
*/
handleTableChange(sorter) {
const { columns } = this.state;
this.setState({ columns: tableSorterChange(sorter, columns) });
}
/**
* 页码改变回调
* @param {number} i
*/
onPageChange(i) {
this.setState({
currentPage: i
});
}
/**
* 显示记录总数
*/
showPagination = (total) => {
return `共${total}条记录`
}
/**
* 点击行事件回调
* @param {number} idx
*/
onRowClick(idx) {
this.setState(preState => {
if (preState.expandedRowKeys[0] === idx) {
return {
expandedRowKeys: [],
rowClassName: ''
};
}
return {
expandedRowKeys: [idx],
rowClassName: (r, i) => {
if (i === idx) return 'noraml-row';
if (i === idx - 1) return 'pre-row';
return '';
}
};
});
}
render() {
const { loading, currentPage, expandedRowKeys } = this.state;
const {
pageSize = 10,
showLength,
getSelectedRows,
pagination,
expandedRowRender,
rowKey= (r, i) => i
} = this.props;
const expandedMethods = expandedRowRender ? {
onRow: (r, i) => {
return {
onClick: () => this.onRowClick(i)
};
},
expandRowByClick: true,
expandIconAsCell: false,
expandedRowKeys: expandedRowKeys,
expandedRowRender: r => this.props.expandedRowRender(r)
} : {}
return (
<div className="table-hoc-wrap">
<WrappedComponent
rowKey={rowKey}
{...this.state}
rowSelection={getSelectedRows ? this.rowSelection() : null}
pagination={
pagination !== false
? (
loading
? {
hideOnSinglePage: true,
current: 1,
pageSize: pageSize
}
: {
hideOnSinglePage: true,
current: currentPage,
pageSize: pageSize,
onChange: page => this.onPageChange(page),
showTotal: this.showPagination
}
)
: pagination
}
onChange={(pagination, filters, sorter) =>
this.handleTableChange(sorter)
}
{...expandedMethods}
showLength={showLength ? true : null}
/>
</div>
);
}
};