因为刚到如今就任的公司接手到公司后台的项目代码的时候,发现系统中大量使用了表格,由于代码经多人参与过,致使有些代码块是经过ul>li标签布局有些地方则是经过div布局等等,致使了代码的严重冗余;简单的表格还好,若是是表格中须要对某些字段进行排序的话,又会大大增长代码的量级和可读性,如图javascript
代码为:java
从上图部分截图能够看出,一个表格的代码量大概能占到200行代码,并且每一个字段的排序的上下箭头都会增长两个state去进行管理,5个字段须要排序则须要增长10个state,也就意味着每次咱们在进入路由组件的时候和进行各类刷新重置操做的时候都须要去处理这10个state等等,还有一个问题就是表格的布局,每一个字段所占的宽和高度都是class去设置的,也就意味着,每次产品来告诉你,这个表格须要增长一个字段,你也就得从新去计算全部的字段所占宽度和分配适合的宽度。以上所述的问题违背了程序开发的耦合性和复用性,因此决定封装一个Table组件去解决这个问题。react
一、传入一个数组自动构建出表头以及该表头下这一列的显示的内容,命名为columns,数据类型为array
二、传入表格须要显示的数据源数组,命名为dataSource,数据类型为array
三、缺省状态下,表格须要展现的内容,命名为emptyText,数据类型为string
四、传入表格的分页项,包含当前页码,总页码,以及分页器的点击事件,命名为pagination,数据类型为object
五、传入一个表格每行勾选的配置对象,包含是否使用勾选、勾选的点击事件,便于知足对表格勾选项进行批量处理的需求,命名为rowSelection,数据类型为object
六、传入一个表格行的点击事件,便于知足点击当前行进入详情的需求,命名为onRowClick,数据类型为function
七、传入表格的样式对象,给表格内部的外层包裹容器添加行内样式,命名为style,数据类型为object
八、传入一个className给表格内部的外层包裹容器添加className,能够实如今表格组件外部设置表格的每一列的className,数据类型为string数组
一、总体代码app
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ListNone from '../ListNone/ListNone';
import Pager from '../Pager/Pager';
import './Table.less';
// API
// columns为表格定义数据格式,title字段为表格标题,dataIndex为传入的数据源中须要显示的字段一致,能够经过render函数来渲染当前列的数据 -> Array// dataSource为数据源 -> Array
// rowSelection列表项是否可选 -> Object | null
// pagination为分页器 -> Object | false
// onRowClick为单行点击事件
export default class Table extends Component {
constructor(props) {
super(props);
this.state = {
rowAllSelect: false,//全选按钮
rowCheck: [],//单项勾选框
rowSelId: [], //选中的id
sortArr: [], //排序标志数组
};
}
static propTypes = {
columns: PropTypes.array.isRequired, //表头名称
dataSource: PropTypes.array.isRequired, //数据列表
emptyText: PropTypes.string, //列表为空时表格缺省状态
pagination: PropTypes.object, //表格分页对象,包含当前页码,总共页数,分页器的点击事件,
rowSelection: PropTypes.object, //表格单选全选的配置对象,onChange为单选全选框的状态改变事件,能够获得选中的列的数据 style: PropTypes.object, //表格的样式对象
isLastNoOp: PropTypes.bool //表格最后一行不须要渲染操做样式
}
static defaultProps = {
dataSource: [],
columns: [],
pagination: {},
emptyText: '暂无相关信息'
};
componentWillReceiveProps(nextProps) {
let { dataSource } = nextProps;
let { rowCheck } = this.state;
let sortArr = [];
if (dataSource != this.props.dataSource) {
dataSource.map((item, i) => {
rowCheck[i] = false;
});
this.props.columns.map(item => {
sortArr.push("");
});
const rowSelId = [];
this.props.rowSelection && this.props.rowSelection.onChange(rowSelId);
this.setState({
rowAllSelect: false,//全选按钮
rowCheck,//单项勾选框
rowSelId, //选中的id
sortArr
});
}
}
//单选
goodsChange = (item, index, event) => {
let { rowCheck } = this.state;
let status = false;
let { rowSelId } = this.state;
if (event.target.checked) {
rowCheck[index] = true;
rowSelId.push(item);
} else {
rowCheck[index] = false;
for (let i = 0; i < rowSelId.length; i++) {
if (rowSelId[i].id == item.id) {
rowSelId.splice(i, 1);
break;
}
}
// rowSelId.splice($.inArray(item.id,rowSelId),1);
}
for (let i = 0; i < rowCheck.length; i++) {
if (rowCheck[i]) {
status = true;
} else {
status = false;
break;
}
}
if (status) {
let all = [];
for (let i = 0; i < rowCheck.length; i++) {
all.push(true);
}
rowCheck = all;
}
this.setState({
rowCheck,
rowAllSelect: status,
rowSelId
});
this.props.rowSelection && this.props.rowSelection.onChange(rowSelId);
}
//全选 goodsAllChange = (event) => {
var all = [];
var rowSelId = [];
const { dataSource } = this.props;
var status = false;
if (event.target.checked) {
for (var i = 0; i < dataSource.length; i++) {
all.push(true);
rowSelId.push(dataSource[i]);
}
status = true;
} else {
for (var i = 0; i < dataSource.length; i++) {
all.push(false);
rowSelId = [];
}
status = false;
}
this.setState({
rowCheck: all,
rowAllSelect: status,
rowSelId
});
this.props.rowSelection && this.props.rowSelection.onChange(rowSelId);
event.stopPropagation();
}
trClick = (data, event) => {
this.props.onRowClick && this.props.onRowClick(data, event);
}
sort = (item, type, index, event) => {
let { sortArr } = this.state;
for (let i = 0; i < sortArr.length; i++) {
sortArr[i] = "";
}
sortArr[index] = type;
this.setState({
sortArr
});
item.sorter(type, item.dataIndex);
}
//列筛选
colFilter = (item,event) =>{
item.colFilter.eventL(event.target.value);
}
//递归columns的children
retColumns = (columns) => {
const { sortArr, rowAllSelect } = this.state;
const { rowSelection, bordered } = this.props;
return (
<thead className={bordered && 'bordered'}>
{this.convertToRows(columns).map((row,j) =>{
return (
<tr key={j}>
{rowSelection && <th><input type="checkbox" className={rowAllSelect ? 'checked' : ''} checked={rowAllSelect} onChange={this.goodsAllChange} /></th>}
{row.map((item, i) => {
return (
<th key={i} {...item} className={bordered && 'bordered'}>
<div className={item.sorter?"marginL":""}>{item.title}</div>
{item.sorter &&
<div>
<span className={`sort up${sortArr[i] === 'asc' ? ` top` : ``}`} onClick={this.sort.bind(this, item, "asc", i)}></span>
<span className={`sort down${sortArr[i] === 'desc' ? ` bottom` : ``}`} onClick={this.sort.bind(this, item, "desc", i)}></span>
</div>
}
{item.colFilter &&
<span className="colFilter">
<select onChange={this.colFilter.bind(this,item)}>
{
item.colFilter.data.map((filterItem,k) =>{
return (<option value={filterItem.val} key={k}>{filterItem.name}</option>)
})
}
</select>
</span>
}
</th>
)
})}
</tr>
)
})}
</thead>
)
}
getAllColumns = (columns) => {
const result = [];
columns.forEach((column) => {
if (column.children) {
result.push(column);
result.push.apply(result, this.getAllColumns(column.children));
} else {
result.push(column);
}
});
return result;
}
convertToRows = (originColumns) => {
let maxLevel = 1;
const traverse = (column, parent) => {
if (parent) {
column.level = parent.level + 1;
if (maxLevel < column.level) {
maxLevel = column.level;
}
}
if (column.children) {
let colSpan = 0;
column.children.forEach((subColumn) => {
traverse(subColumn, column);
colSpan += subColumn.colSpan;
});
column.colSpan = colSpan;
} else {
column.colSpan = 1;
}
};
originColumns.forEach((column) => {
column.level = 1;
traverse(column);
});
const rows = [];
for (let i = 0; i < maxLevel; i++) {
rows.push([]);
}
const allColumns = this.getAllColumns(originColumns);
allColumns.forEach((column) => {
if (!column.children) {
column.rowSpan = maxLevel - column.level + 1;
} else {
column.rowSpan = 1;
}
rows[column.level - 1].push(column);
});
return rows;
};
retRows = (columns, data, index, isLastNoOp, length) =>{
return (
this.getAllColumns(columns).map((col, i) => {
if (col.dataIndex !== undefined) {
if (data[col.dataIndex] !== undefined) {
return (<td key={i} width={col.width && col.width}>{col.render ? col.render(data[col.dataIndex], data, index) : data[col.dataIndex]}</td>)
} else {
return (<td key={i} width={col.width && col.width}></td>)
}
} else {
if (col.children && col.children.length > 0 ) {
this.retRows(col.children, data);
} else {
let renderEle = "";
if (col.render) {
renderEle = col.render(data, index);
if(isLastNoOp && index === length - 1){
renderEle = "";
}
}
return (<td key={i} width={col.width && col.width}>{col.render && renderEle}</td>);
}
}
})
);
}
render () {
const {
className,
columns,
dataSource,
pagination,
rowSelection,
style,
emptyText,
isLastNoOp
} = this.props;
const { rowCheck } = this.state;
return (
<div className={`table-box ${className}`}>
<table style={style}>
{/* 表头部分 */}
{this.retColumns(columns)}
<tbody>
{
dataSource.map((data, i) => {
return (
<tr key={i} onClick={this.trClick.bind(this, data)}>
{rowSelection && <td><input type="checkbox" className={rowCheck[i] ? 'checked' : ''} checked={rowCheck[i]} onChange={this.goodsChange.bind(this, data, i)} /></td>}
{this.retRows(columns, data, i, isLastNoOp, dataSource.length)}
</tr>
)
})
}
</tbody>
</table>
<ListNone list={dataSource} text={emptyText} />
<Pager {...pagination} />
</div>
);
}}复制代码
二、表头部分(thead)less
//递归columns的children
retColumns = (columns) => {
const { sortArr, rowAllSelect } = this.state;
const { rowSelection, bordered } = this.props;
return (
<thead className={bordered && 'bordered'}> {this.convertToRows(columns).map((row,j) =>{ return ( <tr key={j}> {rowSelection && <th><input type="checkbox" className={rowAllSelect ? 'checked' : ''} checked={rowAllSelect} onChange={this.goodsAllChange} /></th>} {row.map((item, i) => { return ( <th key={i} {...item} className={bordered && 'bordered'}> <div className={item.sorter?"marginL":""}>{item.title}</div> {item.sorter && <div> <span className={`sort up${sortArr[i] === 'asc' ? ` top` : ``}`} onClick={this.sort.bind(this, item, "asc", i)}></span> <span className={`sort down${sortArr[i] === 'desc' ? ` bottom` : ``}`} onClick={this.sort.bind(this, item, "desc", i)}></span> </div> } {item.colFilter && <span className="colFilter"> <select onChange={this.colFilter.bind(this,item)}> { item.colFilter.data.map((filterItem,k) =>{ return (<option value={filterItem.val} key={k}>{filterItem.name}</option>) }) } </select> </span> } </th> ) })} </tr> ) })} </thead> ) }复制代码
注意:convertToRows函数是为了处理多表头的状况,接收咱们传入Table组件的columns,经过递归columns中每一项的children字段并断定每一列的colSpan,最终返回一个新的columns进行表头的渲染函数
三、表体部分(tbody)布局
retRows = (columns, data, index, isLastNoOp, length) =>{
return (
this.getAllColumns(columns).map((col, i) => {
if (col.dataIndex !== undefined) {
if (data[col.dataIndex] !== undefined) {
return (<td key={i} width={col.width && col.width}>{col.render ? col.render(data[col.dataIndex], data, index) : data[col.dataIndex]}</td>)
} else {
return (<td key={i} width={col.width && col.width}></td>)
}
} else {
if (col.children && col.children.length > 0 ) {
this.retRows(col.children, data);
} else {
let renderEle = "";
if (col.render) {
renderEle = col.render(data, index);
if(isLastNoOp && index === length - 1){
renderEle = "";
}
}
return (<td key={i} width={col.width && col.width}>{col.render && renderEle}</td>);
}
}
})
);
} 复制代码
四、表格less部分ui
.table-box{
table{
width: 100%;
thead{
width: 100%;
background-color: #e8e9ed;
tr{
th{
position: relative;
text-align: center;
vertical-align: middle;
padding: 6px;
// input[type=checkbox]{
// width: 25px;
// height: 25px;
// vertical-align: middle;
// }
div:nth-of-type(1){
display: inline-block;
}
div:nth-of-type(2){
cursor: pointer;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: inline-block;
margin-left: 3px;
.sort{
display: block;
width: 0px;
height: 0px;
border-width: 7px;
border-style:solid;
}
.up{
border-color: transparent transparent #868893 transparent;
margin-bottom: 8px;
}
.down{
border-color: #868893 transparent transparent transparent;
}
.top{
border-color: transparent transparent #eb6767 transparent;
}
.bottom{
border-color: #eb6767 transparent transparent transparent;
}
}
.colFilter{
display: inline-block;
margin-left: 10px;
border: 1px solid #d2d6de;
}
}
.bordered{
border: 1px solid #ccc;
}
.marginL{
margin-left: -10px;
}
}
}
tbody{
width: 100%;
background-color: #FFFFFF;
tr{
width: 100%;
border: 1px solid #e4e4e4;
cursor: pointer;
transition: all .3s ease;
td{
text-align: center;
vertical-align: middle;
padding: 6px;
// input[type=checkbox]{
// width: 25px;
// height: 25px;
// vertical-align: middle;
// }
}
}
tr:hover{
background-color: #F9F9F9;
}
}
}}复制代码
如下是在render函数中的代码this
如下是传入的columns的代码(关键)
注意:如需添加表头添加子级表头,只需在columns数组中的其中一项添加children字段,children为一个数组