• 技术文章 >web前端 >前端问答

    react怎么实现表头固定

    藏色散人藏色散人2023-01-09 10:25:11原创59

    react实现表头固定的方法:1、通过Ant Design的Table组件实现表格固定表头;2、使用“rc-table”实现移动端表格表头固定;3、通过监听div的onscroll事件,改变div的scrollLeft属性。

    本教程操作环境:Windows10系统、react18.0.0版、Dell G3电脑。

    react怎么实现表头固定?

    React表格固定表头/锁定列

    Ant Design的Table组件挺好用,固定表头及锁定列的功能不在话下,但Ant Design Mobile没有Table组件。移动端要实现表格固定表头及锁定列的功能应该可以使用rc-table,当然也可以自己写一个。

    通过分析AntD的Table,可以看出固定表头的表格是由上下两个<table>标签组成的,它们分别嵌套在div内,上面的是表头,只包含<thead>,下边是表格内容,只包含<tbody>。应该是通过监听下面div的onscroll事件,改变上面div的scrollLeft属性,这样在水平滚动表格时,表头也会同步滚动。固定列是通过设置th及td的CSS属性position为sticky并且设置left或right为0实现,同时设置z-index,让锁定的列始终显示在上方。

    原理整明白了,写代码就比较容易了。

    components/ScrollableTable/interface.tsx
    
    import * as React from 'react';
    export declare type AlignType = 'left' | 'center' | 'right';
    export interface ColumnType {
      align?: AlignType;
      className?: string;
      dataKey?: string;
      fixed?: boolean;
      title?: React.ReactNode;
      width?: number;
      render?: (value: any, record: any, index: number) => React.ReactNode;
    }
    export interface TableProps {
      className?: string;
      style?: React.CSSProperties;
      columns?: ColumnType[];
      dataSource?: any[];
      width?: number;
      height?: number;
    }
    
    components/ScrollableTable/index.tsx
    
    import React, { FunctionComponent, useRef } from 'react';
    import { TableProps, ColumnType } from './interface';
    import './index.less';
    const ScrollableTable: FunctionComponent<any> = (props: TableProps) => {
      const style: React.CSSProperties = props.style || {};
      const maxHeight: string = props.width ? (props.height + 'px') : 'unset';
      const columns: ColumnType[] = props.columns || [];
      const dataSource: any[] = props.dataSource || [];
      let maxWidth: number = 0;
      if (props.width) style.width = props.width;
      if (columns.length === 0) {
        columns.push({
          dataKey: 'key'
        });
      }
      columns.forEach((column: ColumnType) => {
        const width: number = column.width || 50;
        maxWidth += width;
      });
      const fixedColumns: number[][] = getFixedColumns(columns);
      const leftFixedColumns: number[] = fixedColumns[0];
      const rightFixedColumns: number[] = fixedColumns[1];
      const tableBody: any = useRef();
      const handleScroll = (target: any) => {
        const scrollLeft: number = target.scrollLeft;
        const tableHeaders: any = target.parentElement.getElementsByClassName('st-table-header');
        if (tableHeaders.length > 0) {
          tableHeaders[0].scrollLeft = scrollLeft;
        }
      };
      return (
        <div
          className={classNames('st-table-container', props.className)}
          style={style}
        >
          <div className="st-table-header">
            <table>
              <colgroup>
                {
                  renderCols(columns)
                }
              </colgroup>
              <thead className="st-table-thead">
                <tr>
                  {
                    columns.map((column: ColumnType, index: number) => {
                      const align: any = column.align || undefined;
                      const title: React.ReactNode = column.title || '';
                      const fixed: string = leftFixedColumns.includes(index) ? 'left' : (rightFixedColumns.includes(index) ? 'right' : '');
                      const fixedClassName: string = fixed ? ('st-table-cell-fix-' + fixed) : '';
                      return (
                        <th
                          key={index}
                          className={classNames('st-table-cell', fixedClassName, column.className)}
                          style={{textAlign: align}}
                        >
                          {title}
                        </th>
                      );
                    })
                  }
                </tr>
              </thead>
            </table>
          </div>
          <div
            ref={tableBody}
            className="st-table-body"
            style={{maxHeight: maxHeight}}
            onScroll={(e: any) => handleScroll(e.currentTarget)}
          >
            <table style={{width: maxWidth, minWidth: '100%'}}>
              <colgroup>
                  {
                    renderCols(columns)
                  }
                </colgroup>
                <tbody className="st-table-tbody">
                  {
                    dataSource.map((record: any, index: number) => (
                      <tr key={index} className="st-table-row">
                        {
                          renderCells(columns, leftFixedColumns, rightFixedColumns, record, index)
                        }
                      </tr>
                    ))
                  }
                </tbody>
            </table>
          </div>
        </div>
      );
    };
    function classNames(...names: (string | undefined)[]) {
      const currentNames: string[] = [];
      names.forEach((name: (string | undefined)) => {
        if (name) currentNames.push(name);
      });
      return currentNames.join(' ');
    }
    function getFixedColumns(columns: ColumnType[]) {
      const total: number = columns.length;
      const leftFixedColumns: number[] = [];
      const rightFixedColumns: number[] = [];
      if (columns[0].fixed) {
        for (let i = 0; i < total; i++) {
          if (columns[i].fixed) {
            leftFixedColumns.push(i);
          } else {
            break;
          }
        }
      }
      if (columns[total - 1].fixed) {
        for (let i = total - 1; i >= 0; i--) {
          if (columns[i].fixed) {
            if (!leftFixedColumns.includes(i)) rightFixedColumns.push(i);
          } else {
            break;
          }
        }
      }
      return [leftFixedColumns, rightFixedColumns];
    }
    function renderCols(columns: ColumnType[]) {
      return columns.map((column: ColumnType, index: number) => {
        const width: number = column.width || 50;
        return (
          <col
            key={index}
            style={{width: width, minWidth: width}}
          />
        );
      });
    }
    function renderCells(columns: ColumnType[], leftFixedColumns: number[], rightFixedColumns: number[], record: any, index: number) {
      return columns.map((column: ColumnType, index: number) => {
        const align: any = column.align || undefined;
        const fixed: string = leftFixedColumns.includes(index) ? 'left' : (rightFixedColumns.includes(index) ? 'right' : '');
        const className: string = classNames('st-table-cell', column.className, fixed ? ('st-table-cell-fix-' + fixed) : '');
        const rawValue: any = (column.dataKey && column.dataKey in record) ? record[column.dataKey] : undefined;
        let value: any = undefined;
        if (column.render) {
          value = column.render(rawValue, record, index);
        } else {
          value = (rawValue === undefined || rawValue === null) ? '' : String(rawValue);
        }
        return (
          <td
            key={index}
            className={className}
            style={{textAlign: align}}
          >
            {value}
          </td>
        );
      });
    }
    export default ScrollableTable;
    
    components/ScrollableTable/index.less
    
    .st-table-container {
      border: 1px solid #f0f0f0;
      border-right: 0;
      border-bottom: 0;
      font-size: 14px;
      .st-table-header {
        border-right: 1px solid #f0f0f0;
        overflow: hidden;
        table {
          border-collapse: separate;
          border-spacing: 0;
          table-layout: fixed;
          width: 100%;
          thead.st-table-thead {
            tr {
              th.st-table-cell {
                background: #fafafa;
                border-bottom: 1px solid #f0f0f0;
                border-right: 1px solid #f0f0f0;
                color: rgba(0, 0, 0, .85);
                font-weight: 500;
                padding: 8px;
                text-align: left;
                &:last-child {
                  border-right: 0;
                }
              }
            }
          }
        }
      }
      .st-table-body {
        overflow: auto scroll;
        border-bottom: 1px solid #f0f0f0;
        border-right: 1px solid #f0f0f0;
        table {
          border-collapse: separate;
          border-spacing: 0;
          table-layout: fixed;
          tbody.st-table-tbody {
            tr.st-table-row {
              td.st-table-cell  {
                border-bottom: 1px solid #f0f0f0;
                border-right: 1px solid #f0f0f0;
                color: rgba(0, 0, 0, .65);
                padding: 8px;
                text-align: left;
                &:last-child {
                  border-right: 0;
                }
              }
              &:last-child {
                td.st-table-cell  {
                  border-bottom: 0;
                }
              }
            }
          }
        }
      }
      table {
        .st-table-cell {
          &.st-table-cell-fix-left {
            background: #fff;
            position: sticky;
            left: 0;
            z-index: 2;
          }
          &.st-table-cell-fix-right {
            background: #fff;
            position: sticky;
            right: 0;
            z-index: 2;
          }
        }
      }
    }

    然后可以这样使用:

    views/Test/index.tsx
    import React, { FunctionComponent } from 'react';
    import Page from '../../components/Page';
    import ScrollableTable from '../../components/ScrollableTable';
    import StoreProvider from '../../stores/products/context';
    import './index.less';
    const Test: FunctionComponent<any> = (props: any) => {
      let records: any[] = [{
        id: 1,
        productName: '淡泰',
        amount1: 198,
        amount2: 200,
        amount3: 205.5,
        currency: '人民币',
        ca: 'Amy'
      }, {
        productName: '方润',
        amount1: 105.5,
        amount2: 100,
        amount3: 108,
        currency: '港元',
        ca: 'Baby'
      }, {
        productName: '医疗基金-1',
        amount1: 153,
        amount2: 150,
        amount3: 155,
        currency: '人民币',
        ca: 'Emily'
      }, {
        productName: '医疗基金-2',
        amount1: 302,
        amount2: 300,
        amount3: 290,
        currency: '美元',
        ca: 'Baby'
      }, {
        productName: '医疗基金-3',
        amount1: 108.8,
        amount2: 100,
        amount3: 130,
        currency: '人民币',
        ca: 'Amy'
      }, {
        productName: '医疗基金-4',
        amount1: 205,
        amount2: 200,
        amount3: 208,
        currency: '美元',
        ca: '吴丹'
      }, {
        productName: '医疗基金-5',
        amount1: 315.5,
        amount2: 300,
        amount3: 280,
        currency: '人民币',
        ca: 'Baby'
      }, {
        productName: '医疗基金-6',
        amount1: 109,
        amount2: 95,
        amount3: 106,
        currency: '人民币',
        ca: 'Emily'
      }, {
        productName: '恒大私募债',
        amount1: 213,
        amount2: 200,
        amount3: 208,
        currency: '港元',
        ca: '吴丹'
      }];
      const totalRecord: any = {
        productName: '合计',
        amount1: {},
        amount2: {},
        amount3: {}
      };
      records.forEach((record: any) => {
        const currency: string = record.currency;
        ['amount1', 'amount2', 'amount3'].forEach((key: string) => {
          const value: any = totalRecord[key];
          if (!(currency in value)) value[currency] = 0;
          value[currency] += record[key];
        });
      });
      records.push(totalRecord);
      const columns: any[] = [{
        dataKey: 'productName',
        title: '产品名称',
        width: 90,
        fixed: true
      }, {
        dataKey: 'amount1',
        title: <React.Fragment>上周缴款金额<br/>(万)</React.Fragment>,
        width: 140,
        align: 'center',
        className: 'amount',
        render: calculateTotal
      }, {
        dataKey: 'amount2',
        title: <React.Fragment>上周预约金额<br/>(万)</React.Fragment>,
        width: 140,
        align: 'center',
        className: 'amount',
        render: calculateTotal
      }, {
        dataKey: 'amount3',
        title: <React.Fragment>待本周跟进金额<br/>(万)</React.Fragment>,
        width: 140,
        align: 'center',
        className: 'amount',
        render: calculateTotal
      }, {
        dataKey: 'currency',
        title: '币种',
        width: 80
      }, {
        dataKey: 'ca',
        title: 'CA',
        width: 80
      }];
      return (
        <StoreProvider>
          <Page
            {...props}
            title="销售统计"
            className="test"
          >
            <div style={{padding: 15}}>
              <ScrollableTable
                width={window.innerWidth - 30}
                height={196}
                columns={columns}
                dataSource={records}
              />
            </div>
          </Page>
        </StoreProvider>
      );
    };
    function calculateTotal(value: any) {
      if (value instanceof Object) {
        const keys: any[] = Object.keys(value);
        return (
          <React.Fragment>
            {
              keys.map((key: string, index: number) => (
                <span key={index}>
                  {`${value[key].toFixed(2)}万${key}`}
                </span>
              ))
            }
          </React.Fragment>
        )
      }
      return value.toFixed(2);
    }
    export default Test;
    
    views/Test/index.less
    
    .st-table-container {
      .st-table-body {
        td.st-table-cell.amount {
          padding-right: 20px !important;
          text-align: right !important;
          span {
            display: block;
          }
        }
      }
    }

    推荐学习:《react视频教程

    以上就是react怎么实现表头固定的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:React
    上一篇:react跳转传值怎么实现 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • react怎么设置div高度• react不显示PDF生成信息怎么办• 怎么用react实现引导页• react 怎么实现删除功能• 深入聊聊vue3中的reactive()
    1/1

    PHP中文网