基于React+AntDesign实现的一个自定义的可编辑表格,主要用于数据库表字段的编辑、带翻译功能,稍作修改后也可复用到其他地方。
主要包括三个文件:index.js,EditableTable.js,EditableTable.less,其中图标是使用的阿里巴巴矢量图标库上面的,接入了百度翻译的API.
效果图:
具体代码如下
index.js:
import React, { Component } from "react";
import EditableTable from "./EditableTable";
class EditTable extends Component {
onDataSourceChanged = data => {
};
render() {
const selectData = [
{ label: '常用', options: ['VARCHAR', 'CHAR', 'INT', 'DATE'] },
{ label: '整数', options: ['INT', 'BIGINT', 'BIT', 'TINYINT'] },
{ label: '实数', options: ['DECIMAL', 'DOUBLE', 'FLOAT'] },
{ label: '字符串', options: ['VARCHAR', 'CHAR'] },
{ label: '二进制', options: ['BLOB', 'BINARY'] },
{ label: '日期时间', options: ['DATE', 'TIMESTAMP', 'DATETIME'] },
{ label: '空间几何', options: ['POINT', 'LINESTRING'] },
{ label: '其他', options: ['ENUM', 'SET'] },
];
const columns = [
{
editable: '1',
dataIndex: 'chName',
title: '中文名',
type: 'input',
width: 150,
required: true
}, {
editable: '2',
dataIndex: 'name',
title: '字段名',
type: 'input',
width: 150,
required: true
},
{
editable: '3',
dataIndex: 'type',
title: '字段类型',
type: 'select',
data: selectData,
width: 200,
required: true
},
{
editable: '4',
dataIndex: 'primaryKey',
title: '主键',
type: 'checkbox',
width: 50,
}, {
editable: '5',
dataIndex: 'foreignKey',
title: '外键',
type: 'checkbox',
width: 50
}, {
editable: '6',
dataIndex: 'notNull',
title: '非空',
type: 'checkbox',
width: 50
}
];
return <div style={{ display: 'flex', margin: '100px 200px' }}>
<EditableTable columns={columns} rowKey={'name'} onDataSourceChanged={this.onDataSourceChanged}/>
</div>;
}
}
export default EditTable;
EditableTable.js:
import React from 'react';
import { Checkbox, Form, Input, message, Select, Table, Tooltip } from 'antd';
import styles from './EditableTable.less';
import md5 from "md5";
import fetchJsonp from "fetch-jsonp";
const EditableContext = React.createContext();
const { Option, OptGroup } = Select;
const EditableRow = ({ form, index, ...props }) => (
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
);
const EditableFormRow = Form.create()(EditableRow);
class EditableCell extends React.Component {
state = {
editing: false,
checkboxes: {}
};
toggleEdit = () => {
const editing = !this.state.editing;
this.setState({ editing }, () => {
if (editing) {
this.input.focus();
}
});
};
save = (e, dataIndex) => {
const { record, handleSave } = this.props;
this.form.validateFields((error, values) => {
if (error && error[e.currentTarget.id]) {
return;
}
this.toggleEdit();
const value = values;
//中英互译,如不需要翻译,可移除此if分支
if (dataIndex === 'chName') {
const q = value.chName;
if (q.trim().length === 0) {
return;
}
const appid = '12345678';
const salt = '1435660288';
const sign = md5(appid + q + salt + '12345678');
const url = `http://api.fanyi.baidu.com/api/trans/vip/translate?q=${q}&from=zh&to=en&appid=${appid}&salt=1435660288&sign=${sign}`;
fetchJsonp(url)
.then(response => response.json())
.then(data => {
if (data && data.trans_result) {
const result = data.trans_result[0].dst;
if (result) {
value.name = result.split(' ').join('_');
}
handleSave({ ...record, ...value });
}
}
);
} else {
handleSave({ ...record, ...value });
}
});
};
onCheckboxChange = (e, record) => {
const { checkboxes } = this.state;
checkboxes[record.editable_row_key] = e.target.checked;
this.setState({
checkboxes
});
};
renderCell = form => {
this.form = form;
const { children, dataIndex, record, title, type, data = [], required } = this.props;
const optGroups = [];
if (type === 'select' && data.length > 0) {
data.map((item, index) => {
const optGroup = <OptGroup label={item.label} key={index}>
{item.options.map((option, index2) => {
return <Option value={option} key={index2}>{option}</Option>;
})}
</OptGroup>;
optGroups.push(optGroup);
});
}
const { editing } = this.state;
return !editing && type === 'input' ?
(<div
className={styles['editable-cell-value-wrap']}
style={{ paddingRight: 24, minHeight: '30px' }}
onClick={this.toggleEdit}
>
{children}
</div>)
: (
<Form.Item style={{ margin: 0 }}>
{form.getFieldDecorator(dataIndex, {
rules: [
{
required: required !== undefined && required === true,
message: `${title} 必填`,
},
],
//默认值,如果是下拉框则设置默认值为下拉项第一项
initialValue: type === 'select' ? data[0].options[0] : record[dataIndex],
})(
type === 'select' ?
<Select
style={{ width: '200px' }}
ref={node => (this.input = node)}
onPressEnter={this.save}
onBlur={this.save}
>
{optGroups}
</Select>
: type === 'checkbox' ?
<Checkbox
ref={node => (this.input = node)}
onChange={(e) => this.onCheckboxChange(e, record)}
checked={this.state.checkboxes[record.editable_row_key]}
/>
: <Input
maxLength={dataIndex === 'describe' ? 100 : 40}
ref={node => (this.input = node)}
onPressEnter={this.save}
onBlur={e => this.save(e, dataIndex)}
/>
)}
</Form.Item>
);
};
render() {
const {
editable,
dataIndex,
title,
type,
data,
width,
required,
record,
index,
handleSave,
children,
...restProps
} = this.props;
return (
<td {...restProps}>
{editable ? (
<EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>
) : (
children
)}
</td>
);
}
}
class EditableTable extends React.Component {
constructor(props) {
super(props);
const dataSource = (props.dataSource || []).map((data, idx) => {
return {
...data,
editable_row_key: idx + '_' + props.rowKey,
};
});
this.state = {
dataSource,
count: dataSource.length,
};
}
handleDelete = key => {
const dataSource = [...this.state.dataSource];
let newData = dataSource.filter(item => item.editable_row_key !== key);
this.setState({ dataSource: newData });
};
handleAdd = () => {
const { dataSource, count } = this.state;
const newData = {
'editable_row_key': count + '_' + this.props.rowKey,
};
this.setState({
dataSource: [...dataSource, newData],
count: count + 1,
});
};
handleSave = row => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.editable_row_key === item.editable_row_key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
this.setState({ dataSource: newData });
if (this.props.onDataSourceChanged) {
this.props.onDataSourceChanged(newData);
}
};
delete = () => {
const { selectRows = [] } = this.state;
const dataSource = [...this.state.dataSource];
selectRows.forEach((selectRow) => {
dataSource.forEach((item, index) => {
if (selectRow === item.editable_row_key) {
dataSource.splice(index, 1);
}
});
});
this.setState({
dataSource,
selectRows: []
});
};
/**
* 百度在线翻译
*/
translate = () => {
const { selectRows = [], dataSource = [] } = this.state;
if (selectRows.length === 0) {
message.error('请先选中需要翻译的行!');
return;
}
const q = [];
selectRows.forEach((selectRow) => {
dataSource.forEach((item) => {
if (item.editable_row_key === selectRow && item.chName !== undefined && item.chName.trim() !== '') {
q.push(item.chName);
}
});
});
if (q.length === 0) {
return;
}
const appid = '12345678';
const salt = '1435660288';
const sign = md5(appid + q + salt + '12345678');
const url = `http://api.fanyi.baidu.com/api/trans/vip/translate?q=${q}&from=zh&to=en&appid=${appid}&salt=1435660288&sign=${sign}`;
let result;
fetchJsonp(url)
.then(response => response.json())
.then(data => {
if (data && data.trans_result) {
result = data.trans_result[0];
if (result) {
const src = result.src.split(',');
const dst = result.dst.split(',');
dataSource.forEach((item) => {
const index = src.findIndex(item2 => {
return item.chName === item2;
});
if (dst[index]) {
item.name = dst[index].split(' ').join('_');
}
});
this.setState(dataSource);
}
}
}
);
};
render() {
const { dataSource } = this.state;
const components = {
body: {
row: EditableFormRow,
cell: EditableCell,
},
};
const columns = this.props.columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: record => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
type: col.type,
data: col.data,
required: col.required,
ellipsis: true,
width: col.width,
handleSave: this.handleSave,
}),
};
});
columns.push(
{
title: '操作',
render: (text, record) =>
this.state.dataSource.length >= 1 ? (
<a onClick={() => {
this.handleDelete(record['editable_row_key']);
}}>
删除
</a>
) : null,
},
);
const title = (
<div>
<a onClick={this.handleAdd}>
<Tooltip title={'添加字段'}>
<img src={'/img/add.png'} width={30}/>
</Tooltip>
</a>
<a onClick={this.delete} style={{ marginLeft: '10px' }}>
<Tooltip title={'删除字段'}>
<img src={'/img/delete.png'} width={25}/>
</Tooltip>
</a>
<a onClick={this.translate} style={{ marginLeft: '10px' }}>
<Tooltip title={'翻译'}>
<img src={'/img/translate.png'} width={23}/>
</Tooltip>
</a>
<a onClick={this.handleAdd} style={{ marginLeft: '10px' }}>
<Tooltip title={'添加新单词'}>
<img src={'/img/word.png'} width={30}/>
</Tooltip>
</a>
</div>
);
const rowSelection = {
onChange: record => {
this.setState({
selectRows: record
});
}
};
return (
<Table
rowSelection={rowSelection}
rowKey="editable_row_key"
title={() => title}
components={components}
rowClassName={() => styles['editable-row']}
size="middle"
pagination={false}
dataSource={dataSource}
columns={columns}
/>
);
}
}
export default EditableTable;
EditableTable.less:
.editable-cell {
position: relative;
}
.editable-cell-value-wrap {
padding: 5px 12px;
cursor: pointer;
}
.editable-row:hover .editable-cell-value-wrap {
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 4px 11px;
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
}
用到了百度翻译API,如果不用可以直接移除相关代码,需要使用的话去百度翻译开放平台申请,拿到appid和密钥,安装antd,md5等所需依赖,就可以直接使用了