最近因为业务接触了antd,使用antd完成一个复杂的树形表格的显示以及修改。在这其中遇见了不少坑,很多功能antd只写了初步的功能,更为细化的功能只能自己完善。踩过的坑都写在了这里。
树形表格的显示
在antd中对于表格的key值有着严格的控制,每一个row都必须有一个独一无二的key值,可以是数字也可以是字符串。这一点和我曾经使用过得iview有着很大的区别。react使用key来代表每一行是为了避免重新渲染的问题,这个优化也在实际的开发中带来了不少的问题。比如新建行时需要自定义新key。
下面直接上一下代码及代码效果,这是一个三级的树形表格,且其中包含二级标题。
最终效果
colums标题: 简易版标题,随着功能的增加,我们将增加colums的复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | let columns = [ { title: '题目' , dataIndex: 'text' }, { title: '类型' , children: [ { title: '一级' , dataIndex: 'text1' }, { title: '二级' , dataIndex: 'text2' , }] }, { title: '内容' , dataIndex: 'content' }, { title: '答案' , dataIndex: 'answer' , }, { title: '类型' , dataIndex: 'mark_type' , className: 'line' }, { title: '版本' , dataIndex: 'version' , className: 'line' }, { title: '一级内容点' , dataIndex: 'value1' , className: 'line' }, { title: '二级内容点' , dataIndex: 'value2' , className: 'line' }, { title: '操作' , key: 'action' , width: 205 } ]; |
data数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | let data = [{ "key" : 1, "text" : "题目一" , "children" : [{ "key" : 11, "text1" : "数学一" , "children" :[] }, { "key" : 12, "text1" : "语文一" , "value1" : "语文" , "children" : [{ "key" : 121, "value2" : "选择" , "text2" : "选择题" , "content" : "题目内容" , "answer" : "A" , "mark_type" : "1" , "version" : "1" },{ "key" : 122, "value2" : "填空" , "text2" : "填空题" , "content" : "题目内容" , "answer" : "梅花" , "mark_type" : "1" , "version" : "1" },{ "key" : 123, "value2" : "阅读" , "text2" : "阅读题" , "content" : "题目内容" , "answer" : "野蛮生长" , "mark_type" : "1" , "version" : "1" },{ "key" : 124, "value2" : "文言文" , "text2" : "文言文" , "content" : "题目内容" , "answer" : "滕王阁序" , "mark_type" : "1" , "version" : "1" }], }], }, { "key" : 2, "text" : "题目二" , "children" : [ { "key" : 21, "text1" : "英语一" , "value1" : "英语" , "children" : [{ "key" : 211, "value2" : "完型" , "text2" : "完形填空" , "content" : "题目内容" , "answer" : "ABC" , "mark_type" : "2" , "version" : "1" },{ "key" : 212, "value2" : "一级代码" , "text2" : "选择" , "content" : "题目内容" , "answer" : "D" , "mark_type" : "2" , "version" : "1" }], }], }]; |
增加子项数据
增加子项数据使用操作中的增加按钮进行增加,增加按钮设置为图形,更为形象具体清晰化。
1 2 3 4 | //button样式使用antd自带icon样式 <Button type= "primary" shape= "circle" icon= "plus" size={ 'small' } onClick={ this .handleAdd.bind( this , record)} /> |
注意事项:
1、对于最子项来说没有增加子项选择,需要对不同数据行进行不同处理
2、不同数据行新增数据的内容不同,字段也不同
3、新增之后需要点击确认或者取消
4、加入数据点击确认之后需要添加于当前key下的children中
本想使用antd自带的editable属性,但是这个属性不支持二级标题的编辑,所以只有自己写render
这个问题,在7102年就提出来了,但是已经8102年了快9102年都没有更新。
新增一级类型题目样式
新增二级类型题目样式
这里就部分render代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | //分级标题 { title: '类型' , children: [{ title: '一级' , dataIndex: 'text1' , render: (text, record) => { if ( this .state.isEditing && this .state.editingOneKey === record.key) return <Input defaultValue={text} onChange={(e) => { this .changeEdit(e, record.key, 'text1' ) }} /> return text } }, { title: '二级' , dataIndex: 'text2' , render: (text, record) => { if ( this .state.isEditing && this .state.editingTwoKey === record.key) return <Input defaultValue={text} onChange={(e) => { this .changeEdit(e, record.key, 'text2' ) }} /> return text } }] } //select选择 { title: '内容' , dataIndex: 'content' , render:(text, record) => { if (text === 0) text = '内容一' ; if (text === 1) text = '内容二' ; if ( this .state.isEditing && this .state.editingTwoKey === record.key) { return <Select defaultValue={text} onChange={(e) => { this .changeEdit(e, record.key, 'flag' ) }} getPopupContainer={triggerNode => triggerNode.parentNode}> <Option value= '0' >有效</Option> <Option value= '1' >无效</Option> </Select> } return text } } |
注意展开
如果对一行未展开的行下新增数据,那么无法看到打开的编辑状态是一个非常糟糕的问题。所以我们需要在新增子项的时候自动展开父项。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //steate初始化 expandedRows: [] //展开行 //render 初始化 <Table bordered expandedRowKeys={ this .state.expandedRows} onExpandedRowsChange={ this .changeExpandedRows.bind( this )} /> //自动展开变化,获取当前展开行 changeExpandedRows = (expandedRows) => { this .setState({ expandedRows }) }; //增加函数中 let rows = this .state.expandedRows; rows.push(record.key); this .setState({ expandedRows: rows }); |
删除行数据
删除在antd中相较比较简单,因为antd的table每行都有key属性,key是唯一且必须的属性,所以只要过滤掉包含目的key的数据即可视为删除。因为这是树形数据,普通的遍历无法进行操作,所以使用深度优先遍历来进行数据处理,也可以使用广度优先,根据数据来变更。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | handleDelete = (key) => { let data = this .state.data; data = dsFilter(data, key); this .setState({ data }); function dsFilter(dealData, dealKey) { for (let i = 0; i < dealData.length; i++) { if (dealData[i].children && dealData[i].children.length > 0) { dealData[i].children = dsFilter(dealData[i].children, dealKey); } } return dealData.filter(item => item.key !== dealKey); } }; |
修改某些字段
修改和新增的colums相同,不过修改需要保存之前的值。需要在input或select中设置defaultValue={text}来保证初始值相同
保存
无论是新增修改还是删除,都需要进行保存。这里使用✓和✗ 来代表确定和取消。
同时需要注意,如果在一行的编辑中点击其他行的操作,需要将之前行的操作视为取消。
1 2 3 4 5 6 7 8 | < div > < Button shape = "circle" icon = "check" size={'small'} style={{ backgroundColor: '#65BF34', color: '#FFF', border: 'none' }} onClick={this.saveEdit.bind(this, record.key)} /> < Button shape = "circle" icon = "close" size={'small'} style={{ backgroundColor: '#FF3333', color: '#FFF', border: 'none' }} onClick={this.cancelEdit.bind(this, record.key)} /> </ div > |
修改顺序
修改顺序就是修改数据在数组中的顺序,使用简单的temp进行交换即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | shiftUp = (key) => { let data = this .state.data; data = dsShift(data, key); this .setState({ data }); function dsShift(dealData, dealKey) { for (let i = 0; i < dealData.length; i++) { if (dealData[i].children && dealData[i].children.length > 0) { dealData[i].children = dsShift(dealData[i].children, dealKey); } if (dealData[i].key === dealKey) { if (i === 0) { message.warning( '该行已置顶!' ); break ; } let temp = dealData[i - 1]; dealData[i - 1] = dealData[i]; dealData[i] = temp; break ; } } return dealData; } }; shiftDown = (key) => { let data = this .state.data; data = dsShift(data, key); this .setState({ data }); function dsShift(dealData, dealKey) { for (let i = 0; i < dealData.length; i++) { if (dealData[i].children && dealData[i].children.length > 0) { dealData[i].children = dsShift(dealData[i].children, dealKey); } if (dealData[i].key === dealKey) { if (i === dealData.length - 1) { message.warning( '该行已置尾!' ); break ; } let temp = dealData[i + 1]; dealData[i + 1] = dealData[i]; dealData[i] = temp; break ; } } return dealData; } }; |
总结
在进行表格数据操作中,需要进行变量的保存,这里就没有多写了。总的来说antd是个好组件,大部分功能都很齐全,但是很多细节还是需要自己进行完善。
补充知识:Antd(Ant-design),嵌套子表格(expandedRowRender)的异步获取数据
使用阿里的ant-design开源框架,要在表格里面嵌套子表格,需要在用户点击父表格的一行数据后,获取该行的key,然后去异步请求后台的数据用来填充子表格的内容。
如果这样写(省略无关代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | expandedRowRender = (record) => { dispatch({ type: 'flow/getPlanList' , payload: { contractId: record.contract_id, // 该参数是从父表格带过来的key }, callback: () => { const { flow: { data }, } = this .props; this .setState({ secData: data.list, }); console.log( "返回数据(PlanList):" + JSON.stringify( this .state.secData)); } }); return ( <Table columns={secColumns} dataSource={ this .state.secData} pagination={ false } /> ); }; render() { return ( <Card> { this .renderForm()} <div> <Table expandedRowRender={ this .expandedRowRender} loading={loading} rowSelection={rowSelection} dataSource={list} columns={columns} pagination={paginationProps} scroll={{ x: 2500}} size = 'middle' expandRowByClick={ true } onSelect={ this .seFn} /> </div> </Card> ) } |
则会出现不断的发起请求的现象:
这是因为,expandedRowRender 实际上是在 Table 组件的 render 方法中调用的,React render 中用 dispatch 会造成重复调用的问题,dispatch -> setState -> render -> dispatch -> setState -> render,循环往复。所以应该把 dispatch 放在 onExpand 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | onExpand = (expanded, record) => { const { dispatch } = this .props; dispatch({ type: 'flow/getPlanList' , payload: { contractId: record.contract_id, }, callback: () => { const { flow: { data }, } = this .props; this .setState({ secData: data.list , }); console.log( "返回数据(PlanList):" + JSON.stringify( this .state.secData)); } }); } |
但是单纯的这样做,又会带来新的问题,就是子表格的所有数据都变成了相同的。
本人的解决办法是使用键值对。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | onExpand = (expanded, record) => { const { dispatch } = this .props; if (expanded === false ) { // 因为如果不断的添加键值对,会造成数据过于庞大,浪费资源, // 因此在每次合并的时候讲相应键值下的数据清空 console.log( "合并!" ); this .setState({ subTabData: { ... this .state.subTabData, [record.contract_id]: [] , } }); } else { console.log( "展开!" ); dispatch({ type: 'flow/getPlanList' , payload: { contractId: record.contract_id, }, callback: () => { const { flow: { data }, } = this .props; this .setState({ subTabData: { ... this .state.subTabData, [record.contract_id]: data.list , } }); console.log( "返回数据(PlanList):" + JSON.stringify( this .state.subTabData)); } }); } } |
以上这篇React Ant Design树形表格的复杂增删改操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持自学编程网。
- 本文固定链接: https://zxbcw.cn/post/198819/
- 转载请注明:必须在正文中标注并保留原文链接
- QQ群: PHP高手阵营官方总群(344148542)
- QQ群: Yii2.0开发(304864863)