import React, {Component} from 'react'
|
import PropTypes from 'prop-types'
|
import { is, fromJS } from 'immutable'
|
import { Table, Input, Popconfirm, Form, Icon, Radio } from 'antd'
|
import { formRule } from '@/utils/option.js'
|
import Utils from '@/utils/utils.js'
|
import './index.scss'
|
|
const EditableContext = React.createContext()
|
|
const EditableRow = ({ form, index, ...props }) => (
|
<EditableContext.Provider value={form}>
|
<tr {...props} />
|
</EditableContext.Provider>
|
)
|
|
const EditableFormRow = Form.create()(EditableRow)
|
|
class EditableCell extends Component {
|
state = {
|
editing: false
|
}
|
|
toggleEdit = () => {
|
const editing = !this.state.editing
|
this.setState({ editing }, () => {
|
if (editing && this.input && this.input.select) {
|
this.input.select()
|
} else if (editing && this.input && this.input.focus) {
|
this.input.focus()
|
}
|
})
|
}
|
|
save = e => {
|
const { record, handleSave, datatype } = this.props
|
this.form.validateFields((error, values) => {
|
if (datatype === 'number') {
|
Object.keys(values).forEach(key => {
|
values[key] = parseFloat(values[key])
|
})
|
}
|
handleSave({ ...record, ...values })
|
if (error && error[e.currentTarget.id]) {
|
return
|
}
|
this.toggleEdit()
|
})
|
}
|
|
renderCell = form => {
|
this.form = form
|
const { children, dataIndex, record, datatype } = this.props
|
const { editing } = this.state
|
|
let rules = []
|
if (datatype === 'number') {
|
rules.push({
|
pattern: /^(-?\d+)(\.\d+)?$/,
|
message: formRule.input.numbermsg
|
})
|
}
|
|
return editing ? (
|
<Form.Item style={{ margin: 0 }}>
|
{form.getFieldDecorator(dataIndex, {
|
rules: [
|
{
|
required: dataIndex === 'Value' || dataIndex === 'Text',
|
message: 'NOT NULL.',
|
},
|
...rules
|
],
|
initialValue: record[dataIndex]
|
})(<Input ref={node => (this.input = node)} autoComplete="off" onPressEnter={this.save} onBlur={this.save} />)}
|
</Form.Item>
|
) : (
|
<div
|
className="editable-cell-value-wrap"
|
onClick={this.toggleEdit}
|
>
|
{children}
|
</div>
|
)
|
}
|
|
render() {
|
const {
|
editable,
|
dataIndex,
|
title,
|
record,
|
index,
|
handleSave,
|
children,
|
...restProps
|
} = this.props
|
return (
|
<td {...restProps}>
|
{editable ? (
|
<EditableContext.Consumer style={{padding: 0}}>{this.renderCell}</EditableContext.Consumer>
|
) : (
|
children
|
)}
|
</td>
|
)
|
}
|
}
|
|
class EditTable extends Component {
|
static propTpyes = {
|
dict: PropTypes.object, // 字典项
|
type: PropTypes.string, // 表单类型
|
linkSubFields: PropTypes.array, // 关联字段
|
onChange: PropTypes.func // 数据变化
|
}
|
|
state = {
|
columns: [],
|
dataSource: [],
|
count: 0,
|
type: null,
|
linkSubFields: []
|
}
|
|
UNSAFE_componentWillMount () {
|
const { linkSubFields, type, dict } = this.props
|
let data = this.props['data-__meta'].initialValue
|
|
if (!data) {
|
data = []
|
}
|
|
let fields = []
|
let dataItem = data[0] || ''
|
|
if (type === 'select' || type === 'radio') {
|
fields = linkSubFields.map(cell => {
|
return {
|
title: cell.label,
|
dataIndex: cell.field,
|
editable: true,
|
datatype: dataItem && typeof(dataItem[cell.field]) === 'number' ? 'number' : 'string'
|
}
|
})
|
}
|
|
let columns = [
|
{
|
title: 'Value',
|
dataIndex: 'Value',
|
editable: true,
|
datatype: dataItem && typeof(dataItem.Value) === 'number' ? 'number' : 'string'
|
},
|
{
|
title: 'Text',
|
dataIndex: 'Text',
|
editable: true,
|
datatype: dataItem && typeof(dataItem.Text) === 'number' ? 'number' : 'string'
|
},
|
...fields,
|
{
|
title: '操作',
|
align: 'center',
|
width: '20%',
|
dataIndex: 'operation',
|
render: (text, record) =>
|
this.state.dataSource.length >= 1 ? (
|
<div>
|
<span className="operation-btn" title={dict['header.form.up']} onClick={() => this.handleUpDown(record, 'up')} style={{color: '#1890ff'}}><Icon type="arrow-up" /></span>
|
<span className="operation-btn" title={dict['header.form.down']} onClick={() => this.handleUpDown(record, 'down')} style={{color: '#ff4d4f'}}><Icon type="arrow-down" /></span>
|
<Popconfirm
|
overlayClassName="popover-confirm"
|
title={dict['model.query.delete']}
|
onConfirm={() => this.handleDelete(record.key)
|
}>
|
<span style={{color: '#ff4d4f', cursor: 'pointer'}}><Icon type="delete" /></span>
|
</Popconfirm>
|
</div>
|
) : null,
|
}
|
]
|
|
if (type === 'link') {
|
columns.unshift({
|
title: 'ParentID',
|
dataIndex: 'ParentID',
|
editable: true,
|
datatype: dataItem && typeof(dataItem.ParentID) === 'number' ? 'number' : 'string'
|
})
|
}
|
|
this.setState({
|
columns: columns.map(col => {
|
if (col.dataIndex !== 'operation') {
|
col = {...col, ...this.getColumnSearchProps(col)}
|
}
|
return col
|
}),
|
dataSource: data,
|
count: data.length,
|
type: type,
|
linkSubFields: linkSubFields
|
})
|
}
|
|
getColumnSearchProps = column => ({
|
filterDropdown: () => (
|
<div style={{ padding: 8 }}>
|
<Radio.Group onChange={(e) => this.changeDatatype(column, e)} value={column.datatype}>
|
<Radio style={{display: 'block', height: '30px', lineHeight: '30px'}} value="string">
|
字符串
|
</Radio>
|
<Radio style={{display: 'block', height: '30px', lineHeight: '30px'}} value="number">
|
数字
|
</Radio>
|
</Radio.Group>
|
</div>
|
),
|
filterIcon: () => (
|
<Icon type="swap" style={{ color: column.datatype === 'number' ? '#1890ff' : undefined}} />
|
)
|
})
|
|
changeDatatype = (column, e) => {
|
const { columns, dataSource } = this.state
|
let value = e.target.value
|
let _data = dataSource.map(item => {
|
let val = item[column.dataIndex]
|
if (value === 'number') {
|
try {
|
val = parseFloat(val)
|
if (isNaN(val)) {
|
val = ''
|
}
|
} catch {
|
val = ''
|
}
|
} else {
|
val = '' + val
|
}
|
|
item[column.dataIndex] = val
|
|
return item
|
})
|
|
this.setState({
|
dataSource: _data,
|
columns: columns.map(col => {
|
if (col.dataIndex === column.dataIndex) {
|
col.datatype = value
|
}
|
|
if (col.dataIndex !== 'operation') {
|
col = {...col, ...this.getColumnSearchProps(col)}
|
}
|
|
return col
|
})
|
}, () => {
|
this.props.onChange(_data)
|
})
|
}
|
|
handleUpDown = (record, direction) => {
|
const { dataSource } = this.state
|
let index = 0
|
|
let _data = dataSource.filter((item, i) => {
|
if (item.key === record.key) {
|
index = i
|
}
|
|
return item.key !== record.key
|
})
|
if ((index === 0 && direction === 'up') || (index === dataSource.length - 1 && direction === 'down')) {
|
return
|
}
|
|
if (direction === 'up') {
|
_data.splice(index - 1, 0, record)
|
} else {
|
_data.splice(index + 1, 0, record)
|
}
|
|
this.setState({
|
dataSource: _data
|
}, () => {
|
this.props.onChange(_data)
|
})
|
}
|
|
handleDelete = key => {
|
const { dataSource } = this.state
|
let _data = dataSource.filter(item => item.key !== key)
|
|
this.setState({ dataSource: _data }, () => {
|
this.props.onChange(_data)
|
})
|
}
|
|
handleAdd = () => {
|
const { type, count, dataSource } = this.state
|
const newData = {
|
key: Utils.getuuid(),
|
Value: `${count}`,
|
Text: `${count}`
|
}
|
if (type === 'link') {
|
newData.ParentID = `${count}`
|
}
|
|
let _data = [...dataSource, newData]
|
|
this.setState({
|
dataSource: _data,
|
count: count + 1
|
}, () => {
|
this.props.onChange(_data)
|
})
|
}
|
|
handleSave = row => {
|
const newData = [...this.state.dataSource]
|
const index = newData.findIndex(item => row.key === item.key)
|
const item = newData[index]
|
newData.splice(index, 1, {
|
...item,
|
...row
|
})
|
this.setState({ dataSource: newData }, () => {
|
this.props.onChange(newData)
|
})
|
}
|
|
resetColumn = (type, linkSubFields) => {
|
let dataSource = JSON.parse(JSON.stringify(this.state.dataSource))
|
let fields = []
|
|
if ((type === 'select' || type === 'radio') && linkSubFields.length > this.state.linkSubFields) {
|
let addcol = linkSubFields.slice(-1)[0]
|
dataSource = dataSource.map(data => {
|
data[addcol.field] = data.Text
|
return data
|
})
|
}
|
|
let dataItem = dataSource ? dataSource[0] : ''
|
|
if (type === 'select' || type === 'radio') {
|
fields = linkSubFields.map(field => {
|
return {
|
title: field.label,
|
dataIndex: field.field,
|
editable: true,
|
datatype: dataItem && typeof(dataItem[field.field]) === 'number' ? 'number' : 'string'
|
}
|
})
|
}
|
|
let columns = [
|
{
|
title: 'Value',
|
dataIndex: 'Value',
|
editable: true,
|
datatype: dataItem && typeof(dataItem.Value) === 'number' ? 'number' : 'string'
|
},
|
{
|
title: 'Text',
|
dataIndex: 'Text',
|
editable: true,
|
datatype: dataItem && typeof(dataItem.Text) === 'number' ? 'number' : 'string'
|
},
|
...fields,
|
{
|
title: '操作',
|
align: 'center',
|
width: '20%',
|
dataIndex: 'operation',
|
render: (text, record) =>
|
this.state.dataSource.length >= 1 ? (
|
<div>
|
<span className="operation-btn" title={this.props.dict['header.form.up']} onClick={() => this.handleUpDown(record, 'up')} style={{color: '#1890ff'}}><Icon type="arrow-up" /></span>
|
<span className="operation-btn" title={this.props.dict['header.form.down']} onClick={() => this.handleUpDown(record, 'down')} style={{color: '#ff4d4f'}}><Icon type="arrow-down" /></span>
|
<Popconfirm
|
overlayClassName="popover-confirm"
|
title={this.props.dict['model.query.delete']}
|
onConfirm={() => this.handleDelete(record.key)
|
}>
|
<span style={{color: '#1890ff', cursor: 'pointer'}}><Icon type="delete" /></span>
|
</Popconfirm>
|
</div>
|
) : null,
|
}
|
]
|
|
if (type === 'link') {
|
columns.unshift({
|
title: 'ParentID',
|
dataIndex: 'ParentID',
|
editable: true,
|
datatype: dataItem && typeof(dataItem.ParentID) === 'number' ? 'number' : 'string'
|
})
|
}
|
|
this.setState({
|
columns: columns.map(col => {
|
if (col.dataIndex !== 'operation') {
|
col = {...col, ...this.getColumnSearchProps(col)}
|
}
|
return col
|
}),
|
dataSource: dataSource,
|
type: type
|
}, () => {
|
this.props.onChange(dataSource)
|
})
|
}
|
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
if (!is(fromJS(this.props.linkSubFields), fromJS(nextProps.linkSubFields)) || this.props.type !== nextProps.type) {
|
this.resetColumn(nextProps.type, nextProps.linkSubFields)
|
}
|
}
|
|
render() {
|
const { dataSource } = this.state
|
const components = {
|
body: {
|
row: EditableFormRow,
|
cell: EditableCell
|
}
|
}
|
const columns = this.state.columns.map(col => {
|
if (!col.editable) {
|
return col
|
}
|
return {
|
...col,
|
onCell: record => ({
|
record,
|
editable: col.editable,
|
dataIndex: col.dataIndex,
|
title: col.title,
|
datatype: col.datatype,
|
handleSave: this.handleSave,
|
})
|
}
|
})
|
return (
|
<div className="common-modal-edit-table">
|
<Icon className="add-row" type="plus" onClick={this.handleAdd} />
|
<Table
|
components={components}
|
rowClassName={() => 'editable-row'}
|
bordered
|
dataSource={dataSource}
|
columns={columns}
|
pagination={false}
|
/>
|
</div>
|
)
|
}
|
}
|
|
export default EditTable
|