解决react表格嵌套主表和子表都可以新增、编辑、保存、删除

1. 简述

作者查阅资料,花了几天时间,解决了多个问题。现分享出来,供大家参考。支持新增行、单行编辑保存、展开、分页国际化;子表格增加、编辑、删除、保存。(包含组件,不包含store)

2. 说明

2.1 index.js 里有测试数据
2.2 使用后端数据可以把测试数据删除,修改有store的地方,后端返回json,支持分页等

3. 代码(index.js、TableLayout.js)

3.1 index.js

import React, {Component, useState, useCallback, useRef, PureComponent} from 'react';
import Tablelayout from 'components/TableLayout';
import {observer, inject} from 'mobx-react';
import {message, Table} from 'antd';
import {Form, LocaleProvider, Input,Button, Select, Popconfirm, Divider, Icon,} from "antd";
import '../../../css/iconfont.css';
import {toJS} from "mobx";
import '../index.less';

import zn_CN from 'antd/lib/locale-provider/zh_CN';
import TableLayout from "../../../components/TableLayout";
const {Item, FormItem} = Form;
const {Option} = Select;
const {Provider, Consumer} = React.createContext(); //组件之间传值
var dataType = 0;
const EditableRow = ({form, index, ...props}) => (
    <Provider value={form}>
        <tr {...props} />
    </Provider>
);
// 以下为测试数据
var isLoading =false;
var pageInfo={pageIndex: 1, pageSize: 10, total:100}
var productList=[{
    id:1,
    productKey:'1001',
    productName:'产品1'
}, {
    id:2,
    productKey:'1002',
    productName:'产品2'
}]
var tableData=[{
        id:1,
        productId:1,
        productKey:'1',
        fruitName:'哈密瓜',
        test1:'产地1',
        test2:'味道1',
        test3:'测试1',
        test4:'测试1',
        conversionRatio:0.1,
        isFruit:'是',
        sortNum:1,
    subInfos:[{
        id:100,
        fruitName:'西州哈密瓜',
        test11:'测试',
        test22:'测试',
        test33:'测试',
        description:''
    },{
        id:101,
        fruitName:'东湖哈密瓜',
        test11:'测试',
        test22:'测试',
        test33:'测试',
        description:''
    }]
    }, {
    id:2,
    productId:1,
    fruitName:'草莓',
    test1:'产地2',
    test2:'味道2',
    test3:'测试2',
    test4:'测试2',
    conversionRatio:0.1,
    isFruit:'是',
    sortNum:2
}, {
    id:3,
    productId:1,
    fruitName:'胡萝卜',
    test1:'产地3',
    test2:'味道3',
    test3:'测试3',
    test4:'测试3',
    conversionRatio:0.1,
    isFruit:'否',
    sortNum:3
}
]

@inject('ConfigManageStore')
@observer
class TableRow extends Component {
    constructor(props) {
        super(props)
        this.store = this.props.ConfigManageStore;
        this.state = {
            productKey: '1001',
            productName: '产品1',
            parentTabId: '',
            dataType: 0,
            editFlag: '',
            value: '',
            expandedRowKeys: [],
            dataSource: [{fruitName: ''}]
        };
        this.handleAdd = this.handleAdd.bind(this);
        this.handleDel = this.handleDel.bind(this);
        this.baseColumns = [
            {title: 'id', dataIndex: 'id', editable: true, className: 'hide_col'},
            {title: 'productId', dataIndex: 'productId', editable: true, className: 'hide_col'},
            {title: '名称', dataIndex: 'fruitName', editable: true, align: 'center', width: 200},
            {title: '测试1', dataIndex: 'test1', editable: true, align: 'center'},
            {title: '测试2', dataIndex: 'test2', editable: true, align: 'center',width:100},
            {title: '测试3', dataIndex: 'test3', editable: true, align: 'center'},
            {title: '测试4', dataIndex: 'test4', editable: true, align: 'center',},
            {title: '精度', dataIndex: 'conversionRatio', editable: true, align: 'center'},
            {title: '是否属于水果', dataIndex: 'isFruit', editable: true, align: 'center'},
            {title: '排序', dataIndex: 'sortNum', editable: true, align: 'center', width: 100},
            {
                title: '操作', width: 200, align: 'center', dataIndex: 'operation', render: (text, record, index) => {
                    const {editFlag} = this.state;
                    const editable = this.isEditing(record);
                    return <div>{this.editable(editable, editFlag, record)}</div>
                }
            }
        ];
        this.detailColumns = [
            {title: 'id', dataIndex: 'id', key: 'id', editable: true, className: 'hide_col'},
            {title: '名称', dataIndex: 'fruitName', key: 'fruitName', editable: true}
        ]
        this.childColumns = [
            {title: 'id', dataIndex: 'id', key: 'id1', editable: true, className: 'hide_col'},
            {
                title: '名称',
                dataIndex: 'fruitName',
                key: 'fruitName1',
                editable: true,
                align: 'center',
                width: 150
            },
            {title: '测试11', dataIndex: 'test11', editable: true, align: 'center'},
            {title: '测试22', dataIndex: 'test22', editable: true, align: 'center'},
            {title: '测试33', dataIndex: 'test33', editable: true, align: 'center'},
            {title: '描述', dataIndex: 'description', editable: true, align: 'center', width: 200},
            {
                title: '操作', width: 150, align: 'center', dataIndex: 'operation', render: (text, record, index) => {
                    const {editFlag} = this.state;
                    const editable = this.isEditing(record);
                    return <div>{this.editable(editable, editFlag, record)}</div>
                }
            }];
    }

    componentDidMount() {
        let sendData = {
            productKey: "1001",
            pageIndex: 1,
            pageSize: 10
        };
        this.store.getDataSource(0, sendData);
        this.store.getSelectProductList();
    }

    componentWillUnmount() {
        this.store.tableData = []
        this.store.pageInfo = {
            pageIndex: 1,
            pageSize: 10
        }
    }

    //编辑
    edit = (id) => {
        this.setState({
            editFlag: id
        })
    };

    //保存
    save = (form, record) => {

        this.props.form.validateFields((error, row) => {
            if (error) {
                return
            }
            let newData = []
            const {productKey, dataType} = this.state
            newData = this.store.tableData

            if (dataType == 0) {
                let param = {
                    productKey,
                    ...row
                }
                this.store.editGreenFruit(param);
                const editBean = this.store.editBean;
                if (record.id == null) {
                    newData.splice(0, 1, {
                        editBean,
                        ...row,
                    });
                } else {
                    const index = newData.findIndex(item => item.id === record.id);
                    if (index > -1) {
                        const item = newData[index];
                        newData.splice(index, 1, {
                            ...row,
                        });
                    }
                }

                this.setState({
                    editFlag: '',
                });
            } else {
                const {parentTabId} = this.state;
                const index = newData.findIndex(item => item.id === parentTabId);
                if (index > -1) {
                    const item = newData[index].subInfos;
                    for (let i in item) {
                        if (item[i].id == record.id) {
                            newData[index].subInfos.splice(i, 1, {
                                ...row,
                            });
                            break;
                        }
                    }
                }

                let param = {
                    contentId: Number(parentTabId),
                    ...row
                }
                this.store.editFruit(param);
                this.setState({
                    editFlag: '',
                })
            }
            const {pageIndex,pageSize}=this.store.pageInfo
            this.store.getDataSource(dataType, {productKey, pageIndex:pageIndex, pageSize:pageSize})
        })
    };

    //取消
    cancel = () => {
        const {editFlag, dataType} = this.state;
        const {tableData} = this.store;

        if (editFlag == null) {
            if (dataType == 0) {
                tableData.shift();
            } else {
                const {parentTabId} = this.state
                const index = tableData.findIndex(item => item.id === parentTabId);
                tableData[index].subInfos.pop();
            }
        }
        this.setState({
            editFlag: ''
        })

    };

    handleAdd() {
        const {editFlag, dataType} = this.state;
        if (editFlag !== '') {
            message.warn("请先保存", 1)
            return;
        }
        let tableData = this.store.tableData
        if (dataType == 0) {
            tableData.unshift({
                fruitName: '',
                test1: '',
                test2: '',
                test3: '',
                test4: '',
                conversionRatio: '',
                isFruit: '',
                sortNum: ''
            });

        } else {
            const {parentTabId} = this.state
            const index = tableData.findIndex(item => item.id === parentTabId);
            const subInfos = [];
            if (index > -1) {
                tableData[index].subInfos.push({
                    fruitName: '',
                    test1: '',
                    test2: '',
                    test3: '',
                    description: ''
                });
            }
        }
        this.setState({editFlag: null});
    }

    handleSearch = () => {
        let {productKey, dataType} = this.state
         const {productList} = toJS(this.store);
        const index = productList.findIndex(item => item.productKey === productKey);
        this.store.getDataSource(dataType, {productKey, pageIndex: 1, pageSize: 10});
        this.setState({productName: productList[index].productName})
    };

    handleDel(record) {
        const newData = this.store.tableData
        if (this.state.dataType == 0) {
            this.store.deleteGreenFruitField({id: record.id})
            const index = newData.findIndex(item => item.id === record.id);
            if (index > -1) {
                const item = newData[index];
                newData.splice(index, 1);
            }
        } else {
            this.store.deleteFruitField({id: record.id})
            const index = newData.findIndex(item => item.id === this.state.parentTabId);
            const subInfos = [];
            if (index > -1) {
                const item = newData[index].subInfos;
                for (let i in item) {
                    if (item[i].id == record.id) {
                        newData[index].subInfos.splice(i, 1);
                        break;
                    }
                }
            }
        }
    }

    onPageChange = (pageIndex, pageSize) => {
        let {productKey, dataType} = this.state;
        this.store.getDataSource(dataType, {productKey, pageIndex, pageSize})
    }

    onSelectDataType = (value) => {
        let {productKey} = this.state
        this.setState({dataType: Number(value)}, () => {
            this.store.getDataSource(value, {productKey, pageIndex: 1, pageSize: 10})
        })
    }


    // 选中产品下拉菜单
    onSelectProduct = (value) => {
        this.setState({productKey: value});
    }

    //判断是否可编辑
    isEditing = record => record.id == this.state.editFlag
    editable = (editable, editFlag, record) => {
        const ele = editable ? (
            <span style={{width: 100, display: 'block'}}>
        <Consumer>
          {
              form => (
                  <a onClick={() => this.save(form, record)} style={{marginRight: 8}}>
                      保存
                  </a>
              )
          }
        </Consumer>
        <Popconfirm
            title="是否确定取消?"
            onConfirm={this.cancel}
            okText="确定"
            cancelText="取消"
        >
          <a>取消</a>
        </Popconfirm>
      </span>
        ) : (
            <span>
            <Icon type="edit" disabled={editFlag !== ''} onClick={() => this.edit(record.id)}/>
                <Divider type="vertical"/>
                   <Popconfirm
                       title="是否确定删除?"
                       onConfirm={(e) => {
                           this.handleDel(record)
                       }} //点击确认的回调
                       okText="确定"
                       cancelText="取消"
                   >
                       <Icon type="delete"/>
                   </Popconfirm>
                </span>
        );
        return ele
    }

    expandTable = (expanded, record) => {
        this.setState({parentTabId: record.id})
        // 如果已经展开则收回,否则展开
        let expandedRows = this.state.expandedRowKeys;
        if (expandedRows.includes(record.id)) {
            this.setState({expandedRowKeys: []})
        } else {
            // 获取数据并展开当前行
            let arr = []
            arr.push(record.id)
            this.setState({expandedRowKeys: arr})
        }
    }

    expandedRowRender = (record) => {
        // 子列表也要编辑(字列表不用编辑可以不用,省略了一些子列表编辑代码)
        // const childTableRow = ({form, index, ...props}) => (
        //      <Provider value={form}>
        //       <tr {...props} />
        //     </Provider>
        // );
        //
        // const childTableFormRow = Form.create()(childTableRow);
        const childComponents = {
            body: {
               // row: childTableFormRow,
                cell: EditTableCell
            },
        };

        const childNewColumns = this.childColumns.map((col) => {
            if (!col.editable) {
                return col;
            }
            return {
                ...col,
                onCell: record => ({
                    record,
                    Inputs: Input,
                    dataIndex: col.dataIndex,
                    title: col.title,
                    editing: this.isEditing(record),
                }),
            };
        });

        const childData = record.subInfos;

        return (
            <div style={{border: '0'}}>
                <TableLayout
                    columns={childNewColumns}
                    dataSource={childData}
                    rowKey={record => record.id}
                    components={childComponents}
                    bordered={false}
                />
                <div style={{marginTop: 10}}>
                    <Button onClick={this.handleAdd}>+新增</Button>
                </div>
            </div>
        );
    }

    render() {
       // 使用store,不用测试数据时 打开注释
      // const {isLoading, pageInfo, tableData, productList} = toJS(this.store);
        const {expandedRowKeys} = this.state
        let columns1 = this.state.dataType === 0 ? this.baseColumns : this.detailColumns;

        const components = {
            body: {
               //  row: EditableRow,
                cell: EditTableCell,
            },
        };

        const columns = columns1.map(col => {
            if (!col.editable) {
                return col;
            }
            return {
                ...col,
                onCell: record => ({
                    record,
                    Inputs: Input,
                    dataIndex: col.dataIndex,
                    title: col.title,
                    editing: this.isEditing(record),
                }),
            };
        });

        return (
            <Provider value={this.props.form}>
                <div className="tab-content-bg">
                    <div className='search_bar' style={{marginBottom: '20px'}}>
                        <Form layout="inline">
                            <Select
                                style={{width: '150px', marginRight: 10, position: 'relative'}}
                                placeholder='选择数据类型'
                                allowClear
                                showSearch
                                className='setStart'
                                defaultValue="产品1"
                                onChange={this.onSelectProduct}
                            >
                                {productList.length > 0 &&
                                productList.map(item => (
                                    <Option key={item.productKey} value={item.productKey}>
                                        {item.productName}
                                    </Option>
                                ))}
                            </Select>
                            <Select
                                style={{width: 150, marginRight: 10}}
                                allowClear
                                showSearch
                                optionFilterProp="children"
                                onChange={this.onSelectDataType}
                                defaultValue="0"
                            >
                                <Option value='0'>基本信息</Option>
                                <Option value='1'>详细信息</Option>
                            </Select>
                            <Button type='primary' onClick={this.handleSearch} className="btn-border-purple">
                                查询
                            </Button>
                        </Form>
                        <div style={{float: 'right'}}>
                            {this.state.dataType === 0 ? <Button onClick={this.handleAdd}>+新增</Button> : <div></div>}
                        </div>
                    </div>
                    <i className="iconfont" style={{float: 'left'}}>&#xe61d;</i>
                    <span className="ant-title" style={{
                        float: 'left',
                        fontWeight: 'bold',
                        fontSize: 14
                    }}>&nbsp;{this.state.productName} </span>
                    <LocaleProvider locale={zn_CN}>
                        {this.state.dataType === 0 ? <Tablelayout
                            rowKey={record => record.id}
                            loading={isLoading}
                            components={components}
                            columns={columns}
                            dataSource={tableData}
                            pagination={{
                                ...pageInfo,
                                onChange: this.onPageChange,
                                onShowSizeChange: this.onPageChange,
                                showSizeChanger: true,
                                pageSizeOptions: ['10', '20', '50', '100', '200'],
                            }}
                            bordered
                        /> : <Tablelayout
                            rowKey={record => record.id}
                            loading={isLoading}
                            components={components}
                            columns={columns}
                            dataSource={tableData}
                            pagination={{
                                ...pageInfo,
                                onChange: this.onPageChange,
                                onShowSizeChange: this.onPageChange,
                                showSizeChanger: true,
                                pageSizeOptions: ['10', '20', '50', '100', '200'],
                            }}
                            expandedRowRender={this.expandedRowRender}
                            expandedRowKeys={expandedRowKeys}            // 展开的行的id
                            defaultExpandedRowKeys={expandedRowKeys}
                            onExpand={this.expandTable}
                            expandIconColumnIndex={-1}
                            bordered
                        />
                        }
                    </LocaleProvider>
                </div>
            </Provider>
        )
    }
}

export default Form.create({
    onValuesChange(props, changeValues, allValues) {
        console.log(allValues);
    }
})(TableRow)

class EditTableCell extends PureComponent {

    renderCell = ({getFieldDecorator}) => {
        const {
            editing, dataIndex, title, Inputs, record, index, children,
            ...restProps
        } = this.props;


        return (
            <td {...restProps}>
                {editing ? (dataIndex=='conversionRatio'?<Item style={{margin: 0,border: 0}}>
                            {getFieldDecorator(dataIndex, {
                                rules: [{
                                    required: true,
                                    pattern: new RegExp(/^\d+\.?(\d{1,3})?$/),
                                    message: '输入小数且小数点后最多保留3位'
                                }],
                                initialValue: record[dataIndex],
                            })(
                                <Inputs type='text' style={{border: '1px solid #CCC'}}/>
                            )}
                        </Item>:<Item style={{margin: 0, border: 0}}>
                        {getFieldDecorator(dataIndex, {
                            rules: [{
                                required: dataIndex=='productId'||dataIndex=='id'||dataIndex=='description'?false:true,
                                pattern: dataIndex=='test1'||dataIndex=='test2'?new RegExp(/^\d{1,2}$/):'',
                                message: dataIndex=='test1'||dataIndex=='test2'?'数字且长度不能大于2':`输入${title}`
                            }],
                            initialValue: record[dataIndex],
                        })(
                            <Inputs type='text' style={{border: '1px solid #CCC'}}/>
                        )}
                    </Item>
                ) : children
                }
            </td>
        );
    };

    render() {
        return <Consumer>{this.renderCell}</Consumer>;
    }
}

3.2 TableLayout.js

import React,{Component} from 'react';
import { Table } from 'antd';
import { omit,isEmpty } from 'lodash';

export default class TableLayout extends Component {
  render(){
    const { pagination,...otherProps} = this.props;
    return <Table
      size='middle'
      {...otherProps}
      locale={{emptyText:' '}}
      pagination={
        !isEmpty(pagination) ? pagination.total > 10 && {
          ...omit(pagination,'pageIndex'),
          current:pagination.pageIndex,
          showTotal:(total,range)=>{
            return <div style={{lineHeight:'26px'}}>
              总共{total}条,每页{pagination.pageSize}</div>
          }
        } :false
      }
      
    />
  }
}
4. 效果图

在这里插入图片描述
在这里插入图片描述
欢迎留言

好的,我会帮你解答这个问题。 首先,我们需要安装React和antd。在命令行中输入以下命令: ``` npm install react antd --save ``` 安装完成后,我们可以开始编写代码了。 首先,在你的React组件中引入antd: ```javascript import { Table } from 'antd'; ``` 然后定义你的表格数据。为了演示,我们可以使用以下示例数据: ```javascript const data = [ { key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park', children: [ { key: '1-1', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', children: [ { key: '1-1-1', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', }, ], }, { key: '1-2', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', }, ], }, { key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park', }, { key: '3', name: 'Joe Black', age: 32, address: 'Sidney No. 1 Lake Park', }, ]; ``` 接下来,定义你的表格列: ```javascript const columns = [ { title: 'Name', dataIndex: 'name', key: 'name', }, { title: 'Age', dataIndex: 'age', key: 'age', }, { title: 'Address', dataIndex: 'address', key: 'address', }, ]; ``` 现在,我们可以开始编写我们的表格了。我们将使用antd的`Table`组件。 ```javascript const App = () => { return ( <Table dataSource={data} columns={columns} expandable={{ expandedRowRender: (record) => { const columns2 = [ { title: 'Name', dataIndex: 'name', key: 'name', }, { title: 'Age', dataIndex: 'age', key: 'age', }, { title: 'Address', dataIndex: 'address', key: 'address', }, ]; return ( <Table columns={columns2} dataSource={record.children} pagination={false} expandable={{ expandedRowRender: (record) => { const columns3 = [ { title: 'Name', dataIndex: 'name', key: 'name', }, { title: 'Age', dataIndex: 'age', key: 'age', }, { title: 'Address', dataIndex: 'address', key: 'address', }, ]; return ( <Table columns={columns3} dataSource={record.children} pagination={false} /> ); }, rowExpandable: (record) => record.children.length > 0, }} /> ); }, rowExpandable: (record) => record.children.length > 0, }} /> ); }; ``` 以上代码实现了一个表格嵌套一层表格之后再嵌套第二层表格的效果。 当你点击第一层表格的展开按钮时,会显示第二层表格。当你点击第二层表格的展开按钮时,会显示第三层表格。 希望这个例子对你有所帮助!
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值