解决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}) => (
        //      
        //       
        //     
        // );
        //
        // 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表格嵌套主表和子表都可以新增、编辑、保存、删除_第1张图片
解决react表格嵌套主表和子表都可以新增、编辑、保存、删除_第2张图片
欢迎留言

你可能感兴趣的:(react.js,前端,javascript)