react antd table 自定义表头功能实现

发布于:2024-05-08 ⋅ 阅读:(25) ⋅ 点赞:(0)

react antd table 自定义表头功能

Ⅰ- 壹 - 功能展示和使用需求

需求描述

基于antd table 实现

自定义 table 的表头 内容 排序 宽度和顺序等 , 可根据自己的需求自己扩展

github:https://github.com/whqgo/ReactAntdTableCustomHeader

功能展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Ⅱ - 贰 - 封装思路

在这里插入图片描述

TaskareaTableColumns: 主要对 弹框内容的封装

app.tsx:使用的方式 和对表头数据的描述

json.ts: 数据文件 主要用于模拟的数据

设计思路

用对象数组描述表格 然后进行解析读取处理 , 这是最原始的数据,

const [taskareaTableColumnsData, setTaskareaTableColumnsData] = useState({
    isShow: false, //控制显示隐藏
    type: 'taskarea',
    fields: fixedFields, // 当前字段的 模型数据  , 目的是为了 对字段的控制, 业务需要, 
    // 用于修改 和默认
    columns: [
      // {
      //     serial: '', // 
      //     lable: '', //表头的名称 多个用 /  隔开
      //     lableRename: '', //表头的 重命名名称 优先级高于lable 
      //     value: [], // 要读取数据的字段key要读取数据的字段key 和 lable 关系对应
      //     expression:'',// 表达式,fields存在的才能做计算
      //     width: '',//宽度
      //     defaultSortOrder: '',// 数据 的 排序方式
      //     style:'',//样式
      //     visible:'',//可视
      //     noRender:true,// 是否 重写 render
      //... 和 antd table columns 配置一样
      // }
      {
        key: 'workDispatchOrdersProcessN.paiGongDanHao',
        id: '1', //
        serial: '1',
        // lable: ['工序名称', '派工单号'], //表头的名称 多个用 /  隔开
        lableRename: '工序/单号', //表头的 重命名名称 优先级高于lable 
        title: '工序/单号', //表头的最终 重命名名称 优先级高于lable 
        value: ['workDispatchOrdersProcessN', 'paiGongDanHao'], // 要读取数据的字段key 和 lable 关系对应
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        visible: true,//可视
        defaultSortOrder: '',// 数据 的 排序方式 
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: {},//样式
        align: 'center',//表头居中
      },
      {
        key: 'yxfw',
        id: '22', // 
        serial: '22', // 
        // lable: ['物品名称'], //表头的名称 多个用 /  隔开
        lableRename: '规格', //表头的 重命名名称 优先级高于lable 
        title: '规格',
        value: ['yxfw'], // 要读取数据的字段key
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: { flex: 3 },//样式
        align: 'center',//表头居中
      },

      {
        title: '操作',
        key: 'action',
        serial: '000000',
        id: 'aaa111bbb222ccc333',
        noRender: true,// 是否 重写 render
        align: 'center',//表头居中
        visible: true,//可视
        width: '300px',
        render: (_: any, record: any) => {
          return <div className='operation-item-content-taskarea'><div style={{ 'justifyContent': 'space-evenly', flex: '1' }}>
            <div className='operation-badge-taskarea1'>

              <div style={{}}>
                <Button type="primary" onClick={() => {
                  console.log("操作====按钮触发", record);
                }} size={'large'}>
                  按钮
                </Button>
              </div>
            </div>
          </div></div>
        },
      },
    ],
    severalRows: 3// 控制合并行数
  })

我们可以在 useEffect进行动态控制 这里我们对 tableColumns 中的数据进行了二次处理 目的是在 这里就能获取到我当前的表头原始的数据,去控制最终要显示的数据处理,例如他的表头 文字 title,我需要根据两个值去判最终的结果,render中是根据我需求去做的处理

  useEffect(() => {
    if (!taskareaTableColumnsData?.columns) return

    if (workDispatchOrdersData?.length) {

      setTableColumns(() => (taskareaTableColumnsData.columns.reduce((preData: any, curData: any) => {

        // 判断是否可视
        if (curData.visible) {
          return curData.noRender ? [...preData, curData] : [...preData, {
            ...curData,
            title: (record: any) => {
              let titleStr = curData?.lableRename || (taskareaTableColumnsData.fields.filter((f: any) => curData.value.includes(f.c))).map((f1: any) => f1.n).join('/')
              return <div>
                {titleStr}
              </div>
            },
            width: curData.width ? !isNaN(curData.width) ? curData.width + 'px' : curData.width : '',
            render: (_: any, record: any, index: any) => {
              return (
                <div className='operation-item-content-taskarea'>
                  <div>
                    {
                      (() => {
                        switch (curData.styleType) {
                          case 'def':
                            return curData?.value?.map((cITem: any, i: number) => {
                              return <div key={i}> {record[cITem]}</div>
                            })
                          case 'number':
                            let socketMessageWSDataFind = null
                            // 获取字段 模型的属性 
                            let filterModels = fixedFields.filter((f: any) => curData?.value.includes(f.c))
                            // 判断是不是 自定义的模型 需要特殊处理
                            let customDefinitionFind: any = filterModels.find((f: any) => f.sourceDataType == "customDefinition")

                            return <div style={{ justifyContent: 'start' }}>
                              {/* <div className='operation-badge-taskarea'>{`${item['dmvl'] || 0}/${item['jiHuaShengChanShuLiang']}`}</div> */}
                              {/*  没有推送的时候 查询模型   实际生产数量/计划生产数量 */}
                              <div className='operation-badge-taskarea'>{curData?.expression ? eval(curData.expression) : curData?.value?.reduce((preValData: any, curValData: any) => {
                                // return preValData + (record[curValData] ? (record[curValData]) : '')
                                return [...preValData, record[curValData] || 0]
                              }, []).join('/')}
                                {/* 审核中 样式样式处理  */}
                                {
                                  customDefinitionFind && <div className={customDefinitionFind?.className}>{eval(customDefinitionFind?.expression) || ''}</div>
                                }
                              </div>
                            </div>
                          default:
                            return curData?.value?.map((cITem: any, i: number) => {
                              return <div key={i}> {record[cITem]}</div>
                            })
                        }
                      })()
                    }

                  </div>
                </div>
              )
            },
            onCell: (record: any, index: number) => {
              const cIndex = taskareaTableColumnsData.columns.findIndex(cItem => cItem.id === curData.id)

              if (curData.styleType === 'def' && cIndex + 1 <= taskareaTableColumnsData.severalRows) {
                try {

                  if (index) { // 不是第一条
                    const preD = workDispatchOrdersData[index - 1], nextD = workDispatchOrdersData[index];
                    if (!preD || !nextD) return {}
                    let preV = '', nextV = '';
                    curData.value.map((item: string) => {
                      preV += preD[item]
                      nextV += nextD[item]
                    })

                    if (preV === nextV) { // 上一条和当前条相等,不渲染
                      return { rowSpan: 0 }
                    }
                  }

                  if (index !== workDispatchOrdersData.length - 1) { // 不是最后一条
                    let unlikeIndex = workDispatchOrdersData.length - index; // 默认全部相等
                    for (let i = index; i < workDispatchOrdersData.length; i++) {
                      const nextD = workDispatchOrdersData[i + 1], currentD = workDispatchOrdersData[i];
                      if (!nextD) break;
                      let currentV = '', nextV = '';
                      curData.value.map((item: string) => {
                        currentV += currentD[item]
                        nextV += nextD[item]
                      })
                      if (i !== workDispatchOrdersData.length - 1 && nextV !== currentV) { // 当前条和下一条不相等,就是需要合并的数
                        unlikeIndex = i - index + 1
                        break;
                      }
                    }
                    return { rowSpan: unlikeIndex }
                  }
                } catch (err) { console.log(err); }
              }
              return {}
            }
          }]
        } else {
          return preData
        }

      }, [])))
    }

  }, [workDispatchOrdersData, taskareaTableColumnsData.columns])

在弹框中 TaskareaTableColumns这个组件,就是把taskareaTableColumnsData.columns 中的配置当成数据去处理的,然后对他的内容做了自定义处理,详情看代码

github:https://github.com/whqgo/ReactAntdTableCustomHeader

Ⅲ - 叁 - 主要代码

APP.tsx使用

import { useEffect, useState } from 'react'
import './App.css'
import { Button, Checkbox, Input, InputNumber, Modal, Select, Space, Table } from 'antd';
import TaskareaTableColumns from './TaskareaTableColumns';
import { cloneDeep } from 'lodash'
import { fixedFields, MNData } from './json'
function App() {
  const [taskareaTableColumnsData, setTaskareaTableColumnsData] = useState({
    isShow: false, //控制显示隐藏
    type: 'taskarea',
    fields: fixedFields, // 当前字段的 模型数据  , 目的是为了 对字段的控制, 业务需要, 
    // 用于修改 和默认
    columns: [
      // {
      //     serial: '', // 
      //     lable: '', //表头的名称 多个用 /  隔开
      //     lableRename: '', //表头的 重命名名称 优先级高于lable 
      //     value: [], // 要读取数据的字段key要读取数据的字段key 和 lable 关系对应
      //     expression:'',// 表达式,fields存在的才能做计算
      //     width: '',//宽度
      //     defaultSortOrder: '',// 数据 的 排序方式
      //     style:'',//样式
      //     visible:'',//可视
      //     noRender:true,// 是否 重写 render
      //... 和 antd table columns 配置一样
      // }
      {
        key: 'workDispatchOrdersProcessN.paiGongDanHao',
        id: '1', //
        serial: '1',
        // lable: ['工序名称', '派工单号'], //表头的名称 多个用 /  隔开
        lableRename: '工序/单号', //表头的 重命名名称 优先级高于lable 
        title: '工序/单号', //表头的最终 重命名名称 优先级高于lable 
        value: ['workDispatchOrdersProcessN', 'paiGongDanHao'], // 要读取数据的字段key 和 lable 关系对应
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        visible: true,//可视
        defaultSortOrder: '',// 数据 的 排序方式 
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: {},//样式
        align: 'center',//表头居中
      },
      {
        key: 'workDispatchOrdersItemN',
        id: '2', // 
        serial: '2', // 
        // lable: ['物品名称'], //表头的名称 多个用 /  隔开
        lableRename: '物品', //表头的 重命名名称 优先级高于lable 
        title: '物品',
        value: ['workDispatchOrdersItemN'], // 要读取数据的字段key
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: { flex: 3 },//样式
        align: 'center',//表头居中
      },
      {
        key: 'yxfw',
        id: '22', // 
        serial: '22', // 
        // lable: ['物品名称'], //表头的名称 多个用 /  隔开
        lableRename: '规格', //表头的 重命名名称 优先级高于lable 
        title: '规格',
        value: ['yxfw'], // 要读取数据的字段key
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: { flex: 3 },//样式
        align: 'center',//表头居中
      },
      {
        key: 'oiwo',
        id: '222', // 
        serial: '222', // 
        // lable: ['物品名称'], //表头的名称 多个用 /  隔开
        lableRename: '库存代码', //表头的 重命名名称 优先级高于lable 
        title: '库存代码',
        value: ['oiwo'], // 要读取数据的字段key
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: { flex: 3 },//样式
        align: 'center',//表头居中
      },
      {
        key: 'cqsw',
        id: '2222', // 
        serial: '2222', // 
        // lable: ['物品名称'], //表头的名称 多个用 /  隔开
        lableRename: '客户简称', //表头的 重命名名称 优先级高于lable 
        title: '客户简称',
        value: ['cqsw'], // 要读取数据的字段key
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: { flex: 3 },//样式
        align: 'center',//表头居中
      },
      {
        key: 'frbz.sene',
        id: '3',
        serial: '3',
        // lable: ['主设备名称'], //表头的名称 多个用 /  隔开
        lableRename: '主设备/设备部位', //表头的 重命名名称 优先级高于lable 
        title: '主设备',
        value: ['frbz', 'sene'], // 要读取数据的字段key
        expression: '',// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'def',//样式风格  def 默认文本  number 数字样式
        style: {},//样式
        align: 'center',//表头居中
      },
      {
        key: 'dmvl.jiHuaShengChanShuLiang.mvit',
        id: '4',
        serial: '4',
        lable: ['实际生产数量', '计划生产数量', '审核中'], //表头的名称 多个用 /  隔开
        lableRename: '实际生产数量/计划生产数量', //表头的 重命名名称 优先级高于lable 
        title: '实际生产数量/计划生产数量',
        value: ['dmvl', 'jiHuaShengChanShuLiang', 'mvit'], // 要读取数据的字段key
        expression: "`${socketMessageWSDataFind ? (socketMessageWSDataFind['dmvl'] || record['dmvl'] || 0) : record['dmvl'] || 0}/${record['jiHuaShengChanShuLiang'] || 0}`",// 表达式,fields存在的才能做计算
        width: '',//宽度
        defaultSortOrder: '',// 数据 的 排序方式
        visible: true,//可视
        styleType: 'number',//样式风格  def 默认文本  number 数字样式
        style: { textAlign: 'start', flex: 3 },//样式\
        align: 'center',//表头居中
      },
      // {
      //     key: 'dmvl.jiHuaShengChanShuLiang',
      //     id: '5',
      //     serial: '5',
      //     lable: ['实际生产数量', '计划生产数量'], //表头的名称 多个用 /  隔开
      //     lableRename: '实际生产数量12/计划生产数量333', //表头的 重命名名称 优先级高于lable 
      //     value: ['dmvl', 'jiHuaShengChanShuLiang'], // 要读取数据的字段key
      //     expression: "",// 表达式,fields存在的才能做计算
      //     width: '',//宽度
      //     defaultSortOrder: '',// 数据 的 排序方式
      //     visible: true,//可视
      //     styleType: 'number',//样式风格  def 默认文本  number 数字样式
      //     style: { textAlign: 'start', flex: 3 },//样式\
      //     align: 'center',//表头居中
      // },

      {
        title: '操作',
        key: 'action',
        serial: '000000',
        id: 'aaa111bbb222ccc333',
        noRender: true,// 是否 重写 render
        align: 'center',//表头居中
        visible: true,//可视
        width: '300px',
        render: (_: any, record: any) => {
          return <div className='operation-item-content-taskarea'><div style={{ 'justifyContent': 'space-evenly', flex: '1' }}>
            <div className='operation-badge-taskarea1'>

              <div style={{}}>
                <Button type="primary" onClick={() => {
                  console.log("操作====按钮触发", record);
                }} size={'large'}>
                  按钮
                </Button>
              </div>
            </div>
          </div></div>
        },
      },
    ],
    severalRows: 3// 控制合并行数
  })
  // 最终的 columns
  const [tableColumns, setTableColumns] = useState<any>([])
  const taskareaTableColumnsFillData = (FillDatType: any, tableColumnsFillData: any) => {
    // console.log(tableColumnsFillData, "===tableColumnsFillData===");
    // console.log(FillDatType, "===isShow===");
    // console.log(taskareaTableColumnsData, "===isShow===");

    if (FillDatType) {
      setTaskareaTableColumnsData((taskareaTableColumnsData: any) => {

        let _taskareaTableColumnsData = cloneDeep(taskareaTableColumnsData)

        _taskareaTableColumnsData.isShow = false
        _taskareaTableColumnsData.columns = [...tableColumnsFillData.tableData, _taskareaTableColumnsData.columns[_taskareaTableColumnsData.columns?.length - 1]]
        _taskareaTableColumnsData.severalRows = tableColumnsFillData.severalRows

        return _taskareaTableColumnsData

      })
    } else {
      setTaskareaTableColumnsData((taskareaTableColumnsData: any) => ({ ...taskareaTableColumnsData, isShow: false }))
    }
  }
  const [workDispatchOrdersData, setWorkDispatchOrdersData] = useState<any>([])//数据



  // 初始化数据
  const getInit = async () => {
    setWorkDispatchOrdersData(() => MNData)
  }



  useEffect(() => {
    if (!taskareaTableColumnsData?.columns) return
    // console.log(taskareaTableColumnsData.columns, "====taskareaTableColumnsData.columns===");
    // console.log(workDispatchOrdersData, "====taskareaTableColumnsData.columns===");


    if (workDispatchOrdersData?.length) {

      setTableColumns(() => (taskareaTableColumnsData.columns.reduce((preData: any, curData: any) => {

        // 判断是否可视
        if (curData.visible) {
          return curData.noRender ? [...preData, curData] : [...preData, {
            ...curData,
            title: (record: any) => {
              let titleStr = curData?.lableRename || (taskareaTableColumnsData.fields.filter((f: any) => curData.value.includes(f.c))).map((f1: any) => f1.n).join('/')
              return <div>
                {titleStr}
              </div>
            },
            width: curData.width ? !isNaN(curData.width) ? curData.width + 'px' : curData.width : '',
            render: (_: any, record: any, index: any) => {

              // console.log(_, record, index);
              // console.log(curData)

              return (
                <div className='operation-item-content-taskarea'>
                  <div>
                    {
                      (() => {
                        switch (curData.styleType) {
                          case 'def':

                            return curData?.value?.map((cITem: any, i: number) => {
                              return <div key={i}> {record[cITem]}</div>
                            })
                          case 'number':
                            let socketMessageWSDataFind = null


                            // 获取字段 模型的属性 
                            let filterModels = fixedFields.filter((f: any) => curData?.value.includes(f.c))
                            // 判断是不是 自定义的模型 需要特殊处理
                            let customDefinitionFind: any = filterModels.find((f: any) => f.sourceDataType == "customDefinition")



                            return <div style={{ justifyContent: 'start' }}>
                              {/* <div className='operation-badge-taskarea'>{`${item['dmvl'] || 0}/${item['jiHuaShengChanShuLiang']}`}</div> */}
                              {/*  没有推送的时候 查询模型   实际生产数量/计划生产数量 */}
                              <div className='operation-badge-taskarea'>{curData?.expression ? eval(curData.expression) : curData?.value?.reduce((preValData: any, curValData: any) => {
                                // return preValData + (record[curValData] ? (record[curValData]) : '')
                                return [...preValData, record[curValData] || 0]
                              }, []).join('/')}
                                {/* 审核中 样式样式处理  */}
                                {
                                  customDefinitionFind && <div className={customDefinitionFind?.className}>{eval(customDefinitionFind?.expression) || ''}</div>
                                }
                              </div>



                            </div>

                          default:
                            return curData?.value?.map((cITem: any, i: number) => {
                              return <div key={i}> {record[cITem]}</div>
                            })
                        }
                      })()
                    }

                  </div>
                </div>
              )
            },
            onCell: (record: any, index: number) => {
              const cIndex = taskareaTableColumnsData.columns.findIndex(cItem => cItem.id === curData.id)

              if (curData.styleType === 'def' && cIndex + 1 <= taskareaTableColumnsData.severalRows) {
                try {

                  if (index) { // 不是第一条
                    const preD = workDispatchOrdersData[index - 1], nextD = workDispatchOrdersData[index];
                    if (!preD || !nextD) return {}
                    let preV = '', nextV = '';
                    curData.value.map((item: string) => {
                      preV += preD[item]
                      nextV += nextD[item]
                    })

                    if (preV === nextV) { // 上一条和当前条相等,不渲染
                      return { rowSpan: 0 }
                    }
                  }

                  if (index !== workDispatchOrdersData.length - 1) { // 不是最后一条
                    let unlikeIndex = workDispatchOrdersData.length - index; // 默认全部相等
                    for (let i = index; i < workDispatchOrdersData.length; i++) {
                      const nextD = workDispatchOrdersData[i + 1], currentD = workDispatchOrdersData[i];
                      if (!nextD) break;
                      let currentV = '', nextV = '';
                      curData.value.map((item: string) => {
                        currentV += currentD[item]
                        nextV += nextD[item]
                      })
                      if (i !== workDispatchOrdersData.length - 1 && nextV !== currentV) { // 当前条和下一条不相等,就是需要合并的数
                        unlikeIndex = i - index + 1
                        break;
                      }
                    }
                    return { rowSpan: unlikeIndex }
                  }
                } catch (err) { console.log(err); }
              }
              return {}
            }
          }]
        } else {

          return preData
        }


      }, [])))
    }

  }, [workDispatchOrdersData, taskareaTableColumnsData.columns])


  useEffect(() => {
    getInit()
  }, [])
  return (
    <div className="App">

      {
        taskareaTableColumnsData.isShow && <TaskareaTableColumns data={taskareaTableColumnsData} fillData={taskareaTableColumnsFillData} />
      }
      <div>
        <Button onClick={() => {
          setTaskareaTableColumnsData((data) => ({ ...data, isShow: true }))
        }}> 设置表头</Button>
      </div>
      {
        tableColumns && <Table
          bordered
          columns={tableColumns as any}
          className='taskarea-table-css'
          dataSource={workDispatchOrdersData}
          pagination={false}
          rowKey='id'
        />
      }
    </div>
  )
}

export default App

TaskareaTableColumns 封装

import React, { useState, useEffect, Fragment, useRef, useCallback } from 'react';
import './index.css';
import { Button, Checkbox, Input, InputNumber, Modal, Select, Space, Table } from 'antd';

import update from 'immutability-helper';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

import { cloneDeep } from 'lodash';


interface DraggableBodyRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
    index: number;
    moveRow: (dragIndex: number, hoverIndex: number) => void;
}

const DraggableBodyRowType = 'DraggableBodyRow';

const DraggableBodyRow = ({
    index,
    moveRow,
    className,
    style,
    ...restProps
}: DraggableBodyRowProps) => {
    const ref = useRef<HTMLTableRowElement>(null);
    const [{ isOver, dropClassName }, drop] = useDrop({
        accept: DraggableBodyRowType,
        collect: monitor => {
            const { index: dragIndex } = monitor.getItem() || {};
            if (dragIndex === index) {
                return {};
            }
            return {
                isOver: monitor.isOver(),
                dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
            };
        },
        drop: (item: { index: number }) => {
            moveRow(item.index, index);
        },
    });
    const [, drag] = useDrag({
        type: DraggableBodyRowType,
        item: { index },
        collect: monitor => ({
            isDragging: monitor.isDragging(),
        }),
    });
    drop(drag(ref));
    return (
        <tr
            ref={ref}
            className={`${className}${isOver ? dropClassName : ''}`}
            style={{ cursor: 'move', ...style }}
            {...restProps}
        />
    );
};

const index: any = (props: any) => {
    const { fillData, type, model, data } = props;
    const [state, setState] = useState<any>({
        modalWidth: '60%',
        modalHeight: '55vh',
    });
    const [dataSource, setDataSource] = useState<any[]>([]);
    const [severalRows, setSeveralRows] = useState<any>()
    useEffect(() => {
        console.log(data.severalRows);

        setSeveralRows(data.severalRows)
    }, [data.severalRows])

    // 新增一条
    const addTableRow = () => {
        setDataSource((dataSource: any) => {
            let _dataSource = cloneDeep(dataSource)
            _dataSource.push({
                id: Date.now(),
                serial: _dataSource.length + 1,
                lable: null,
                visible: true,
                align: 'center',//表头居中
            })

            return _dataSource
        })
    }
    // 最终的 columns
    const [tableColumns, setTableColumns] = useState<any>([
        {
            title: '序号',
            key: 'index',
            dataIndex: 'index',
            width: '60px',
            render: (_: any, __: any, index: number) => index + 1, // 使用 index + 1 作为序号值
        },
        {
            title: '字段名称',
            dataIndex: 'value',
            key: 'value',
            noRender: true,
            styleType: 'multipleSelect',

        },
        {
            title: '显示名称',
            dataIndex: 'lableRename',
            key: 'lableRename',
            noRender: true,
            styleType: 'input',

        },
        {
            title: '可视',
            dataIndex: 'visible',
            key: 'visible',
            styleType: 'checkbox',
            noRender: true,

        },
        {
            title: '宽度',
            dataIndex: 'width',
            key: 'width',
            styleType: 'input',
            noRender: true,

        },
        // {
        //     title: '排序',
        //     dataIndex: 'defaultSortOrder',
        //     key: 'defaultSortOrder',
        //     styleType: 'select',
        //     noRender: true,

        // },
        {
            title: '操作',
            key: 'option',
            width: 100,
            render: (_: any, record: any) => (
                <Space>
                    <Button type="primary" onClick={() => {
                        // 删除
                        setDataSource((dataSource: any) => {
                            let _dataSource = cloneDeep(dataSource)
                            let recindex = _dataSource.findIndex((f: any) => f.id == record.id)

                            _dataSource.splice(recindex, 1)
                            return _dataSource
                        })
                    }} danger>
                        删除
                    </Button>
                </Space>
            ),
        },
    ])
    const moveRow = useCallback(
        (dragIndex: number, hoverIndex: number) => {
            const dragRow = dataSource[dragIndex];
            setDataSource(
                update(dataSource, {
                    $splice: [
                        [dragIndex, 1],
                        [hoverIndex, 0, dragRow],
                    ],
                }),
            );
        },
        [dataSource],
    );


    const onOK = async (type: any) => {
        if (type) {
            // console.log('%c [ 工作台设置提交 tableColumns] 日志', 'font-size:13px; background:#26A08F; color:#fff;', tableColumns);
            // console.log('%c [ 工作台设置提交 dataSource] 日志', 'font-size:13px; background:#26A08F; color:#fff;', dataSource);
            if (fillData) fillData(1, { tableData: cloneDeep(dataSource), severalRows })

        } else {
            if (fillData) fillData(0)
        }

    }


    const h = () => {
        if (!data) return
        switch (data.type) {

            case "taskarea":
                return (
                    <>
                        <div>前<InputNumber style={{ margin: '0 5px 10px' }} min={1} value={severalRows} onChange={(val) => setSeveralRows(parseInt(val))} />列数值相同的时候,进行单元格合并</div>
                        {/*  这里必须加个key 不然会报错, 社区给的解决方案就是这样的 */}
                        <DndProvider key={Math.random()} backend={HTML5Backend}>
                            <Table
                                columns={tableColumns}
                                dataSource={dataSource}
                                pagination={false}
                                components={{
                                    body: {
                                        row: DraggableBodyRow,
                                    },
                                }}
                                onRow={(_, index) => {
                                    const attr = {
                                        index,
                                        moveRow,
                                    };
                                    return attr as React.HTMLAttributes<any>;
                                }}
                            />
                        </DndProvider>
                    </>
                )
            default:
                return <></>
        }
    }

    const getInit = async () => {

        // console.log(data, "===getInit===");
        // 过滤掉 操作
        let newList = data.columns.filter((f: any) => f.id != 'aaa111bbb222ccc333')

        setDataSource(() => newList)

        // 配置 table columns

        setTableColumns(() => (tableColumns.reduce((preData: any, curData: any) => {


            return !curData.noRender ? [...preData, curData] : [...preData, {
                ...curData,

                render: (_: any, record: any, index: any) => {

                    // console.log(_, record, index);
                    // console.log(curData)

                    return <Fragment>
                        {
                            (() => {
                                switch (curData.styleType) {
                                    case 'input':
                                        return <Input value={record[curData.dataIndex]} onChange={(e) => {
                                            setDataSource((dataSource: any) => {
                                                let _dataSource = cloneDeep(dataSource)
                                                let recordfind = _dataSource.find((f: any) => f.id === record.id)
                                                recordfind[curData.dataIndex] = e.target.value
                                                return _dataSource
                                            })
                                        }} placeholder={'请输入'}></Input>

                                    case 'checkbox':
                                        return <Checkbox onChange={(e) => {
                                            setDataSource((dataSource: any) => {
                                                let _dataSource = cloneDeep(dataSource)
                                                let recordfind = _dataSource.find((f: any) => f.id === record.id)
                                                recordfind[curData.dataIndex] = e.target.checked
                                                return _dataSource
                                            })

                                        }} checked={record[curData.dataIndex]}></Checkbox>
                                    case 'multipleSelect':
                                        return <Select
                                            mode="multiple"
                                            allowClear
                                            style={{ width: '100%' }}
                                            defaultValue={record[curData.dataIndex] || []}
                                            onChange={(value: any) => {
                                                // console.log('%c [multipleSelect value ] 日志', 'font-size:13px; background:#26A08F; color:#fff;', value);
                                                // 修改
                                                setDataSource((dataSource: any) => {
                                                    let _dataSource = cloneDeep(dataSource)
                                                    let recordfind = _dataSource.find((f: any) => f.id === record.id)
                                                    recordfind[curData.dataIndex] = value
                                                    // 判断是不是数值类型 赋值样式  目前
                                                    let numberStyle = ['decimal']

                                                    let isnumberStyle = data.fields.filter((f: any) => value.includes(f.c)).find((f2: any) => numberStyle.includes(f2.t))
                                                    if (isnumberStyle) {
                                                        recordfind.styleType = 'def'
                                                    } else {
                                                        recordfind.styleType = 'number'
                                                    }


                                                    return _dataSource
                                                })

                                            }}
                                            options={data.fields.map((item: any) => {

                                                return {
                                                    value: item.c,
                                                    label: item.n,
                                                }
                                            })}
                                        />
                                    case 'select':
                                        return <Select
                                            defaultValue={record[curData.dataIndex] || ''}
                                            style={{ width: 120 }}
                                            allowClear
                                            onChange={(value: any) => {
                                                // 修改
                                                setDataSource((dataSource: any) => {
                                                    let _dataSource = cloneDeep(dataSource)
                                                    let recordfind = _dataSource.find((f: any) => f.id === record.id)
                                                    recordfind[curData.dataIndex] = value

                                                    return _dataSource
                                                })

                                            }}
                                            options={[
                                                {
                                                    value: '',
                                                    label: '无',
                                                },
                                                {
                                                    value: 'descend',
                                                    label: '降序',
                                                },
                                                {
                                                    value: 'ascend',
                                                    label: '升序',
                                                },
                                            ]}
                                        />
                                    default:
                                        return <div>暂无内容</div>
                                }
                            })()
                        }

                    </Fragment>

                }
            }]


        }, [])))

    }

    useEffect(() => {
        if (!data) return
        getInit()
    }, [])

    useEffect(() => {
        // console.log(dataSource, "===dataSource===监听修改");
    }, [dataSource])



    return (
        <>
            <Modal
                width={state.modalWidth}
                style={{ top: 50 }}
                destroyOnClose={true}
                centered={false}
                title={(() => {
                    return '设置列表'
                })()}
                open={data?.isShow}
                // onOk={onOK}
                // onCancel={() => setIsShow(false)}
                footer={(() => {
                    let footer = [
                        <Button key="quxiao3" onClick={() => onOK(0)} size={'large'}>
                            取消
                        </Button>,
                        <Button key="xinzengyitiao" type="primary" onClick={() => addTableRow()} size={'large'}>
                            新增一条
                        </Button>,
                        <Button key="submit" type="primary" onClick={() => onOK(1)} size={'large'}>
                            确定
                        </Button>
                    ]
                    return footer
                })()}
                maskClosable={false}
                className="TaskareaTableColumnsCss"
                closable={false}
                bodyStyle={{
                    height: state.modalHeight,
                    position: 'relative',
                    display: 'flex',
                    overflow: 'auto',
                }}
            >
                <div style={{ width: '100%' }}>
                    {
                        (data?.isShow) && h()
                    }
                </div>
            </Modal>
        </>
    );
};
export default index

github:https://github.com/whqgo/ReactAntdTableCustomHeader