react0.14+jquery固定表头表格
代码文件index.js如下:
// index.js
import React, { Component, PropTypes } from 'react';
import $ from 'jquery';
import './style.less';
export default class CommonTable extends Component {
static propTypes = {
columns: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
height: PropTypes.number,
rowHeight: PropTypes.number,
className: PropTypes.string
};
static defaultProps = {
height: 300,
rowHeight: 36,
className: '',
};
constructor(props) {
super(props);
this.state = {
scrollbarHeight: 0,
}
}
componentDidMount() {
this._bindEvents();
this._applyColumnWidths();
this._syncScrollPositions(); // 初始同步
}
componentDidUpdate() {
this._applyColumnWidths();
this._syncScrollPositions();
}
componentWillUnmount() {
this._unbindEvents();
}
_bindEvents() {
const $body = $(this.refs.body);
const $headerInner = $(this.refs.headerInner);
const $scrollbar = $(this.refs.scrollbar);
// 表体滚动时,header与底部滚动条同步
$body.on('scroll.commonTable', () => {
const left = $body.scrollLeft();
$headerInner.css('margin-left', -left);
$scrollbar.scrollLeft(left);
});
// 底部滚动条滚动时,body与header同步
$scrollbar.on('scroll.commonTable', () => {
const left = $scrollbar.scrollLeft();
$headerInner.css('margin-left', -left);
$body.scrollLeft(left);
});
$(window).on('resize.commonTable', () => this._applyColumnWidths());
}
_unbindEvents() {
$(this.refs.body).off('.commonTable');
$(this.refs.scrollbar).off('.commonTable');
$(window).off('resize.commonTable');
}
_syncScrollPositions() {
// 保证三者初始 scrollLeft 一致
const $body = $(this.refs.body);
const $scrollbar = $(this.refs.scrollbar);
const $headerInner = $(this.refs.headerInner);
const left = $body.scrollLeft() || $scrollbar.scrollLeft() || 0;
$body.scrollLeft(left);
$scrollbar.scrollLeft(left);
$headerInner.css('margin-left', -left);
}
_applyColumnWidths2() {
const cols = this.props.columns;
const $headerRow = $(this.refs.headerRow);
const $bodyTable = $(this.refs.bodyTable);
const $bodyRows = $bodyTable.find('tr.ct-row');
const containerWidth = $(this.refs.container).innerWidth();
// 计算固定列宽和可伸缩列数
let totalFixed = 0;
let flexibleCount = 0;
cols.forEach(col => {
if (typeof col.width === 'number') totalFixed += col.width;
else flexibleCount++;
});
let finalWidths = [];
if (totalFixed >= containerWidth) {
// 总列宽超过容器,固定列宽 + 横向滚动
finalWidths = cols.map(col =>
typeof col.width === 'number' ? col.width : 100
);
$(this.refs.body).css({ overflowX: 'auto' });
if (this.state.scrollbarHeight === 0) {
this.setState({ scrollbarHeight: 16 });
}
} else {
// 列少,均分剩余空间
const remaining = containerWidth - totalFixed;
const flexWidth = flexibleCount > 0 ? Math.floor(remaining / flexibleCount) : 0;
finalWidths = cols.map(col =>
typeof col.width === 'number' ? col.width : flexWidth
);
$(this.refs.body).css({ overflowX: 'hidden' });
if (this.state.scrollbarHeight === 16) {
this.setState({ scrollbarHeight: 0 });
}
}
const totalWidth = finalWidths.reduce((a, b) => a + b, 0);
// 设置表格宽度等于容器宽度
$bodyTable.css({ tableLayout: 'fixed', width: containerWidth });
$(this.refs.headerRow).closest('table').css({ tableLayout: 'fixed', width: containerWidth });
// 设置表头列宽
$headerRow.find('th').each(function (idx) {
$(this).css({ width: finalWidths[idx], boxSizing: 'border-box' });
});
// 设置表体所有 td 列宽
$bodyRows.each(function () {
$(this).find('td').each(function (idx) {
$(this).css({ width: finalWidths[idx], boxSizing: 'border-box' });
});
});
// 底部滚动条宽度
$(this.refs.scrollbarInner).css({ width: totalWidth, height: 1 });
}
_applyColumnWidths() {
const cols = this.props.columns;
const $headerRow = $(this.refs.headerRow);
const $bodyTable = $(this.refs.bodyTable);
const $bodyRows = $bodyTable.find('tr.ct-row');
const containerWidth = $(this.refs.container).innerWidth();
// 计算固定列宽和可伸缩列数(只算非 fixedWidth 列)
let totalFixed = 0;
let flexibleCount = 0;
cols.forEach(col => {
if (!!col.fixedWidth || typeof col.width === 'number') totalFixed += col.width || 100;
else flexibleCount++;
});
let finalWidths = [];
if (totalFixed >= containerWidth) {
// 总列宽超过容器,固定列宽 + 横向滚动
finalWidths = cols.map(col => {
if (!!col.fixedWidth || typeof col.width === 'number') return col.width;
return 100; // 默认宽度
});
$(this.refs.body).css({ overflowX: 'auto' });
if (this.state.scrollbarHeight === 0) this.setState({ scrollbarHeight: 16 });
} else {
// 列少,均分剩余空间
const remaining = containerWidth - totalFixed;
const flexWidth = flexibleCount > 0 ? Math.floor(remaining / flexibleCount) : 0;
finalWidths = cols.map(col => {
if (!!col.fixedWidth || typeof col.width === 'number') return col.width;
return flexWidth;
});
$(this.refs.body).css({ overflowX: 'hidden' });
if (this.state.scrollbarHeight === 16) this.setState({ scrollbarHeight: 0 });
}
const totalWidth = finalWidths.reduce((a, b) => a + b, 0);
// 设置表格宽度等于容器宽度
$bodyTable.css({ tableLayout: 'fixed', width: containerWidth });
$(this.refs.headerRow).closest('table').css({ tableLayout: 'fixed', width: containerWidth });
// 应用列宽到表头
$headerRow.find('th').each(function (idx) {
$(this).css({ width: finalWidths[idx], boxSizing: 'border-box' });
});
// 应用列宽到表体所有 td
$bodyRows.each(function () {
$(this).find('td').each(function (idx) {
$(this).css({ width: finalWidths[idx], boxSizing: 'border-box' });
});
});
// 底部滚动条宽度
$(this.refs.scrollbarInner).css({ width: totalWidth, height: 1 });
}
renderHeader() {
const { columns } = this.props;
return (
<table className="ct-table ct-header-table" ref="headerTable">
<thead>
<tr ref="headerRow">
{columns.map(col => (
<th
key={col.key || col.dataIndex}
title={col.title || col.key || col.dataIndex}
className="ct-cell"
style={{ textAlign: col.align ? col.align : 'center' }}
>
{col.title || col.key || col.dataIndex}
</th>
))}
</tr>
</thead>
</table>
);
}
renderBody() {
const { data, columns, rowHeight } = this.props;
return (
<table className="ct-table ct-body-table" ref="bodyTable">
<tbody>
{data.map((row, rIdx) => (
<tr className="ct-row" key={rIdx} style={{ height: rowHeight }}>
{columns.map(col => (
<td
key={col.key || col.dataIndex}
className="ct-cell"
title={row[col.key || col.dataIndex] != null ? String(row[col.key || col.dataIndex]) : ''}
style={{ textAlign: col.align ? col.align : 'left' }}
>
{col.render
? col.render(row[col.key || col.dataIndex], row, rIdx)
: row[col.key || col.dataIndex]}
</td>
))}
</tr>
))}
</tbody>
</table>
);
}
render() {
const { className, height } = this.props;
const { scrollbarHeight = 16 } = this.state; // 底部横向滚动条高度
const headerHeight = 36; // 表头固定高度
// 表体高度 = 总高度 - 表头 - 底部滚动条
const bodyHeight = height - headerHeight - scrollbarHeight;
return (
<div className={`common-table ${className}`} ref="container" style={{ height }}>
<div className="ct-header-outer" style={{ height: headerHeight }}>
<div className="ct-header-inner" ref="headerInner">
{this.renderHeader()}
</div>
</div>
{/* 表体 + 底部滚动条占位布局 */}
<div className="ct-body-scroll-wrapper" style={{ height: bodyHeight + scrollbarHeight }}>
<div
className="ct-body-outer"
ref="body"
style={{ height: bodyHeight, overflowY: 'auto', overflowX: 'auto' }}
>
{this.renderBody()}
</div>
{/* 底部滚动条占位 */}
<div className="ct-scrollbar" ref="scrollbar" style={{ height: scrollbarHeight }}>
<div className="ct-scrollbar-inner" ref="scrollbarInner" />
</div>
</div>
</div>
);
}
}
样式style.less如下:
.common-table {
position: relative;
width: 100%;
font-family: Arial, Helvetica, sans-serif;
border: 1px solid #DFE1E6;
background: #fff;
}
.ct-header-outer {
background: #fff;
border-bottom: 1px solid #DFE1E6;
overflow: hidden;
}
.ct-header-inner {
width: 100%;
transition: margin-left 0.05s linear;
}
/* 包裹表体和底部滚动条,使用占位,不覆盖表体内容 */
.ct-body-scroll-wrapper {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
}
/* 表体 */
.ct-body-outer {
width: 100%;
overflow-y: auto;
overflow-x: auto;
flex-shrink: 0;
}
/* 隐藏 body 自带水平滚动条,但显示竖向滚动条 */
.ct-body-outer::-webkit-scrollbar {
width: 8px;
height: 0; /* 隐藏水平滚动条 */
}
.ct-body-outer::-webkit-scrollbar-thumb {
background: #dddddd;
border-radius: 6px;
border: 1px solid #dddddd;
}
.ct-body-outer::-webkit-scrollbar-track {
background: transparent;
}
.ct-body-outer {
scrollbar-width: auto;
-ms-overflow-style: auto;
}
.ct-table {
border-collapse: collapse;
width: 100%;
table-layout: fixed;
}
.ct-header-table th {
padding: 8px 10px;
border-right: 1px solid #DFE1E6;
background: #E9EBF0;
box-sizing: border-box;
font-size: 14px;
color: #333333;
text-align: center;
line-height: 20px;
font-weight: 400;
}
.ct-body-table td {
padding: 8px 10px;
border-right: 1px solid #DFE1E6;
border-bottom: 1px solid #E9EBF0;
box-sizing: border-box;
font-size: 14px;
color: #333333;
line-height: 20px;
font-weight: 400;
}
.ct-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: default;
}
/* 底部滚动条不再绝对定位,占位在表体下方 */
.ct-scrollbar {
width: 100%;
overflow-x: scroll;
overflow-y: hidden;
background: #fafafa;
border-top: 1px solid #ddd;
flex-shrink: 0;
height: 16px;
}
.ct-scrollbar-inner {
height: 1px;
}
浙公网安备 33010602011771号