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'}}></i>
<span className="ant-title" style={{
float: 'left',
fontWeight: 'bold',
fontSize: 14
}}> {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. 效果图
欢迎留言