react+antd的Table组件编辑单元格

**需求:**新增加一行,且单元格可以编辑

**场景:**真实的业务需求(antd 3 版本+react)

**效果图:**1. 默认增加一行时,第一列是下拉选择框,第2 3列是TextArea,图1

  1. 当下拉选择的数据不同,展示出的第4567列也不同

  2. 有直接调接口默认回显数据、有Input输入框形式(当增加一行新的,默认显示图片2的效果。当选择了"纸质普票",这一行就变成图3 的效果。当选择"纸质普票"以外的选项,又会变成图2 的形式。"纸质普票"比较特殊)。

  3. 编辑好当前行,不保存无法再新增一行

代码仅供参考,里面涉及的业务逻辑可以忽略,可以看主要内容

图1:

图2:

图3:

图4:

图5:


完整代码:

javascript 复制代码
// 父级
import EditableFormTable from './EditableFormTable' // 子级

class Editable extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      IsFPLX: '', // 发票类型
      value: [], // 
      disabled: false, // 控制表格的是否可以新增一行
      zyfp: [
        {key:'纸质普票', label:'纸质普票'},
        {key:'纸质专票', label:'纸质专票'},
        {key:'电子专用发票', label:'电子专用发票'},
        {key:'数电专票', label:'数电专票'}  
       ]
    
  
  render() {
    const { value, IsFPLX, disabled, zyfp } = this.state;
    const propsObj = {
      IsFPLX,
      value,
      onChange: (value) => {
        const values = value   
        this.props.onChange([...value]) // 这块是我这表单平台专有的方法,你那可能不适用。这里做更显数据的
      }
      disabled,
      zyfp,
      
    };
    return (
      <div>
        <EditableFormTable {...propsObj} />     
      </div>
    );
  }
}

export default Editable
javascript 复制代码
import { Table, Input, Button, Popconfirm, Form,  } from 'antd';
const EditableContext = React.createContext();

const { TextArea } = Input
const Option = Select.Option

// 子级中的单个单元格
class EditableCell extends React.Component {
    constructor(props) {
    super(props);
    this.state={
     };
  
    //
    handleChange = (e, form) => {
        const { value, record } = this.props;
        if(e === '纸质普票'){
            this.props.changeSelectFlag(3) // 发票代码字段不用非要输入
        } else if (e === '数电专票'){
            this.props.changeSelectFlag(1) // 发票代码字段置灰,不可输入
        } else {
            this.props.changeSelectFlag(0) // 发票代码字段是否输入不作校验
        }
    }
    
    //
    getInput = (form, record) => {
        const type = this.props.inputType()
        const flag = this.props.IsFPLX
        const optionList = this.props.zyfp
        const selectView = optionList && optionList[0] && optionList.map(item => { return (<Option value={item.key}>{item.label}</Option>)})
        let view = null
        if(record.fplx !== '纸质普票'){
            switch(type){
                case 'select':
                    view=(<Select style={{ width: '100%'}} onSelect={this.handleChange(e, form)}>{selectView}</Select>)
                    break;
                case 'TextArea1':
                    view=this.props.selectFlag===1? (<fragment style={{background: this.props.selectFlag===1 ? '#f5f5f5': ''}}><TextArea autoSize disabled={this.props.selectFlag ===1} /></fragment>) : <TextArea autoSize disabled={this.props.selectFlag ===1} onChange={value => this.props.onbillnumChange(value)} />
                    break;
                case 'TextArea2':
                    view = <TextArea autoSize disabled={this.props.selectFlag ===1} onChange={value => this.props.onbillnumChange(value)} />
                    break;
                defalut:
                    view=<Input />
                    break;
            }
        } else {
            switch(type){
                case 'select':
                    view=(<Select style={{ width: '100%'}} onSelect={this.handleChange(e, form)}>{selectView}</Select>)
                    break;
                case 'TextArea1':
                    view=<TextArea autoSize />
                    break;
                case 'TextArea2':
                    view=<TextArea autoSize />
                    break;
                case 'InputNumber3':
                    view=<TextArea autoSize />
                    break;
                case 'InputNumber4':
                    view=<TextArea autoSize />
                    break;
                case 'InputNumber5':
                    view=<TextArea autoSize />
                    break;
                case 'InputNumber6':
                    view=<TextArea autoSize />
                    break;
                default:
                    view=<Input />
                    break;
            }
        }
        return type
    }

    renderCell = (form) => {
        const { editing, dataIndex, title, inputType, record, index, children, onbillnumChange, selectFlag, ...restPropr} = this.proops
        return (
            td {...restProps}>
                {editable ? (
                  <Form.Item>{form.getFieldDecorator(dataIndex, {
                    rules:[] // 注意:这里写规则校验,getInput方法里校验就失效
                    initialValue: record[dataIndex],
                })(this.getInput(form, record))}</Form.Item>
            ) : (
              children
            )}
          </td>        
        )
    }

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






// 子级页面
class EditableFormTable extends React.Component {
  constructor(props) {
    super(props);
    this.state={
      IsFPLX: '', // 发票类型
      value: [], // 
      editingKey: '', // index,标记当前这条数据正在编辑
      selectFlag: 0, // 0:可编辑状态,这个字段控制当下拉选项为"数电专票"时,发票代码字段不可编辑
     };
     this.columns = [
      {
        title: '发票类型',
        dataIndex: 'fplx',
        width: '10%',
        align: 'center',
        editable: true,
        render: (text, record, index) => {
            const { zyfp =[], IsFPLX=''} = this.props;
            return <span>{text}</span>
        }
      },
      {
        title: '发票代码',
        dataIndex: 'billcode',
        width: '10%',
        align: 'center',
        editable: true,
      },
      {
        title: '发票号码',
        dataIndex: 'billnum',
        width: '10%',
        align: 'center',
        editable: true,
       },
       {
          title: '价税合计金额',
          dataIndex: 'je',
          width: '8%',
          align: 'center',
          editable: true,
        },
       {
          title: '增值税额',
          dataIndex: 'se',
          width: '8%',
          align: 'center',
          editable: true,
        },
        {
          title: '未匹配价税合计金额',
          dataIndex: 'dppje',
          width: '8%',
          align: 'center',
          editable: true,
        },
        {
          title: '未匹配税额',
          dataIndex: 'dppse',
          width: '8%',
          align: 'center',
          editable: true,
        },
        {
          title: '核验结果',
          dataIndex: 'result',
          width: '20%',
          align: 'center',
          editable: true,
        },
    ];

   }

    componentWillMount () {
        if(!this.props.disabled){
            this.columns.push({
                title: '操作',
                dataIndex: 'operation',
                width: '15%',
                align: 'center',
                render: (text, record, index) => {
                    const editable = this.isEditing(index)
                    return editable ? (
                        <span>
                            <EditableContext.Consumer>
                                { form => (
                                      <div>
                                        <a onClick={() => this.save(form, index)} style={{ marginRight:8 }}>保存</a>
                                        <a onClick={() => this.deleteFun(form, index)} style={{ color: 'red', marginLeft:'15px' }}>删除</a>
                                      </div>
                                  )}
                            </EditableContext.Consumer>
                        </span>
                    ) : (
                        <div>
                             <a onClick={() => this.edit(index)} disabled={editingKey !==''}>编辑</a>
                              <a onClick={() => this.deleteFun(form, index)} disabled={editingKey !=='' style={{ color: editingKey !== '' ? '' : 'red', marginLeft:'15px' }}>删除</a>
                         </div>
                    )
                }
            })
        }
    }
    
     // 添加一行
    addTableFun = () => {
        this.changeSelectFlag(0) // 还原最初编辑状态 
        if(this.state.editingKey !== ''){
            return alert('请先保存当前行信息')
        }
        const valueList = this.state.value
        valueList.push({
            fplx: '',
            billcode: '',
            billnum: '',
            je: '',
            se: '',
            dppje: '',
            dppse: '',
            result: '',
            editFlag: true, // 新增一行,带个标识(当新增一行点保存后又想编辑,如何识别点击编辑按钮这行就能编辑呢,就通过这个标识)意味着这行正处在可输入状态中
        })
        this.setState({
            value: valueList,
            editingKey: this.state.value.length-1 // 新增的这行的索引
        })
        this.props.onChange(valueList) // 更新表格数据
    }

    // 
        changeSelectFlag = (selectFlag) => {
            this.setState({
                selectFlag,
            })
        }

    // 当更改发票号码、发票代码、发票类型时
    onbillnumChange = (value) => {
        this.props.form.validateFields((error, row) => {
            if(error){
                return;
            }
            if((row && row.fplx !== '纸质普票') || (value && value,fplx !== '纸质普票')){
                if(value && value.isFetch === 0){
                    const obj = { billnum: value.billnum, fplx, billcode}
                    const requestParams = {
                        "paramsObject": {
                              "vatInvaiceList": [obj],
                          },
                         "userId": window.params.userId,
                         "sysId": window.params.sysId,
                    };
                    const that = this
                    // 调接口
                    window.$.ajax({
                        type: "POST",
                        url: '',
                        contentType: "application/json",
                        data: JSON.stringify(requestParams),
                        dataType: "json",
                        success(data) {
                            if(data.resultData){
                                const { editingKey } = this.state;
                                const valueList = this.state.value
                                 valueList[editingKey].billcode = data.resultData[0].billcode
                                valueList[editingKey].billnum = data.resultData[0].billnum
                                valueList[editingKey].je = data.resultData[0].je
                                valueList[editingKey].se = data.resultData[0].se
                                valueList[editingKey].dppje = data.resultData[0].dppje
                                valueList[editingKey].dppse = data.resultData[0].dppse
                                valueList[editingKey].journalNum = data.resultData[0].journalNum
                                valueList[editingKey].result = ''
                                valueList[editingKey].id = data.resultData[0].id
                                valueList[editingKey].vatInvoceId = new Date().getTime()
                                that.props.onChange(valueList)
                                that.setState({
                                    value: valueList,
                                })
                            } else {
                                alert('获取增值税发票信息失败!')
                            }
                        }
                    })
                }
            } else{
                const that = this
                const { editingKey } = this.state;
                const valueList = this.state.value
                valueList[editingKey].vatInvoceId = new Date().getTime() // 创造唯一id
                valueList[editingKey].fplx = row && row.fplx ? row.fplx : ''
                that.props.onChange(valueList)

            }
        })
        return value
    }

    //
    ChangeVal = (val) => {
         this.setState({ value: val })
    }

    // 编辑
    edit = (key) => {
        const valueList = this.state.value
        if(valueList[key] !== '纸质普票'){
            this.onbillnumChange({
                billcode: valueList && valueList[key] && valueList[key].billcode,
                billnum: valueList && valueList[key] && valueList[key].billnum,
                fplx: valueList && valueList[key] && valueList[key].fplx,
                isFetch: 1, // 编辑标识
            })
            this.setState({ editingKey: key })
            
        } else {
           valueList[key].editFlag = true,
           this.setState({
               value: valueList,
                editingKey: key,
           }) 
        }
    }

    // 编辑的索引
    isEditing = (index) => {
        return index === this.state.editingKey
    }

    // 删除
    deleteFun = (index) => {
        const valueList = this.state.value
        valueList.splice(index, 1)
        this.setState({
            value: valueList,
            editingKey: '',
        })
        this.props.onChange(valueList)
    }

    // 保存
    save = (form, index) => {
        form.validateFields((error, row) => {
            if(error){ return;}
            if(!row.fplx){
                alert('请选择发票类型!')
            } else {
                const newData = JSON.parse(JSON.stringify(this.state.value)) // 用个深拷贝,防止嵌套表格里的数据拿不到
                if(index >-1){ // 索引不等于-1,说明表格最少有一条数据
                    const item = newData[index]
                    newData.splice(index, 1, {  // splice有三个参数时,splice方法功能:替换
                        ...item,
                        ...row,
                        editFlag: false,
                    })
                    this.props.onChange(JSON.parse(JSON.stringify(newData)))
                    this.setState({
                        value: newData,
                        editingKey: '',
                    })
                } else { // 对第一次新增数据进行保存
                    newData.push(row)
                    this.props.onChange(JSON.parse(JSON.stringify(newData)))
                    this.setState({
                        value: newData,
                        editingKey: '',
                    })
                }
            }
        })
    }

  render() {
    const { value, IsFPLX, zyfp } = this.state;
    const components = {
        body: {
          cell: EditableCell, // 在最上面,可以看成一个组件
      },
    };
    
     const columns = this.columns.map(col => {
      if (!col.editable) {
        return col;
      }
      return {
        ...col,
        onCell: (record,index) => {
           return {
                // 下面这一块操作另外一个作用是传值,把值和方法传给<EditableCell />这个组件使用
               record,
                inputType: () => {
                    let type=''
                    switch (col.dataIndex) {
                        case 'fplx':
                            type = 'select';
                            break;
                        case 'billcode':
                            type = 'TextArea1';
                            break;
                        case 'billnum':
                            type = 'TextArea2';
                            break;
                        case 'je':
                            type = 'InputNumber3';
                            break;
                        case 'se':
                            type = 'InputNumber4';
                            break;
                        case 'dppje':
                            type = 'InputNumber5';
                            break;
                        case 'dppse':
                            type = 'InputNumber6';
                            break;
                        default:
                            break;
                    }
                    return type
                },
                dataIndex: col.dataIndex,
                title: col.title,
                IsFPLX: this..state.IsFPLX,
                zyfp: this.props.zyfp // 父级传过来的
                onbillnumChange: (value) => this.onbillnumChange(value) // 改变发票号码调接口
                editing: record.fplx==='纸质普票' ? (col.dataIndex==='fplx' || col.dataIndex==='billcode' || col.dataIndex==='billnum' || col.dataIndex==='je' || col.dataIndex==='se' || col.dataIndex==='dppje' || col.dataIndex==='dppse') && this.isEditing(index) : (col.dataIndex==='fplx' || col.dataIndex==='billcode' || col.dataIndex==='billnum') && this.isEditing(index) // 当下拉选择为"纸质普票"时,前7个都可以输入。反之,前三个可以输入
                selectFlag: this.state.selectFlag, // 
                changeSelectFlag: this.changeSelectFlag, // 单元格作为子级想要改变selectFlag的值,所以在这里往单元格组件中传个方法,通过方法改变(大致意思为:子级没办法直接改变父级中的值,但可以通过父级传过去的方法进而改变父级中的值)
                ChangeVal: this.ChangeVal,
                value: this.state.value,
           }
          
        },
      };
    });


    return (
      <div>
        <EditableContext.Provider value={this.props.form}>
          <Table 
             bordered
             pagination={false}
             dataSource={this.state.value} 
             columns={columns}
             components={components}
             rowClassName="editable-row"
          />
        </EditableContext.Provider>
        {this.props.disabled ? '' : 
            <div style={{textAlign: 'center'}}>
                <a onClick={() => {this.addTableFun()}}><Icon type='plus' style={{fontSize: '16px', color: '#08c'}} /></a>
            </div>
        }    
      </div>
    );
  }
}

export default EditableFormTable
相关推荐
m0_748237051 分钟前
Monorepo pnpm 模式管理多个 web 项目
大数据·前端·elasticsearch
JinSoooo4 分钟前
pnpm monorepo 联调方案
前端·pnpm·monorepo
m0_7482449613 分钟前
【AI系统】LLVM 前端和优化层
前端·状态模式
明弟有理想13 分钟前
Chrome RCE 漏洞复现
前端·chrome·漏洞·复现
平行线也会相交14 分钟前
云图库平台(二)前端项目初始化
前端·vue.js·云图库平台
shimmer00815 分钟前
抖音小程序登录(前端通过tt.login获取code换取openId)
前端·小程序·状态模式
嘤嘤怪呆呆狗25 分钟前
【开发问题记录】使用 Docker+Jenkins+Jenkins + gitee 实现自动化部署前端项目 CI/CD(centos7为例)
前端·vue.js·ci/cd·docker·gitee·自动化·jenkins
鱼钓猫的小鱼干30 分钟前
table 表格转成 excell 导出
前端·vue·excel
一只搬砖的猹32 分钟前
cJson系列——常用cJson库函数
linux·前端·javascript·python·物联网·mysql·json
CodeClimb44 分钟前
【华为OD-E卷-租车骑绿道 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od