react+antd的Table组件编辑单元格
需求:新增加一行,且单元格可以编辑
场景:真实的业务需求(antd 3 版本+react)
效果图:1. 默认增加一行时,第一列是下拉选择框,第2 3列是TextArea,图1
2. 当下拉选择的数据不同,展示出的第4567列也不同
3. 有直接调接口默认回显数据、有Input输入框形式(当增加一行新的,默认显示图片2的效果。当选择了“纸质普票”,这一行就变成图3 的效果。当选择“纸质普票”以外的选项,又会变成图2 的形式。“纸质普票”比较特殊)。
4. 编辑好当前行,不保存无法再新增一行
代码仅供参考,里面涉及的业务逻辑可以忽略,可以看主要内容
图1:
图2:
图3:
图4:
图5:
完整代码:
// 父级 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
2.
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