import React, { Component } from 'react'
|
import PropTypes from 'prop-types'
|
import { is, fromJS } from 'immutable'
|
import { DndProvider, DragSource, DropTarget } from 'react-dnd'
|
import { Table, Input, InputNumber, Popconfirm, Form, Select, Radio, Cascader, notification, message, Modal, Typography } from 'antd'
|
import { CopyOutlined, EditOutlined, DeleteOutlined, SnippetsOutlined } from '@ant-design/icons'
|
|
import Utils from '@/utils/utils.js'
|
import ColorSketch from '@/mob/colorsketch'
|
import PasteForm from '@/templates/zshare/pasteform'
|
import asyncComponent from '@/utils/asyncComponent'
|
import CusSwitch from './cusSwitch'
|
import zhCN from '@/locales/zh-CN/model.js'
|
import enUS from '@/locales/en-US/model.js'
|
import MKEmitter from '@/utils/events.js'
|
import './index.scss'
|
|
const MkEditIcon = asyncComponent(() => import('@/components/mkIcon'))
|
let eTDict = sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
|
const EditableContext = React.createContext()
|
const { confirm } = Modal
|
let dragingIndex = -1
|
const { Paragraph } = Typography
|
|
class BodyRow extends React.Component {
|
render() {
|
const { isOver, moveAble, connectDragSource, connectDropTarget, moveRow, ...restProps } = this.props
|
|
let { className } = restProps
|
if (isOver && moveAble) {
|
if (restProps.index > dragingIndex) {
|
className += ' drop-over-downward'
|
}
|
if (restProps.index < dragingIndex) {
|
className += ' drop-over-upward'
|
}
|
}
|
|
if (moveAble) {
|
return connectDragSource(
|
connectDropTarget(<tr {...restProps} className={className} style={{ ...restProps.style, cursor: 'move' }} />),
|
)
|
} else {
|
return (<tr {...restProps} className={className} style={restProps.style} />)
|
}
|
}
|
}
|
|
const rowSource = {
|
beginDrag(props) {
|
dragingIndex = props.index
|
return {
|
index: props.index,
|
}
|
}
|
}
|
|
const rowTarget = {
|
drop(props, monitor) {
|
const dragIndex = monitor.getItem().index
|
const hoverIndex = props.index
|
|
if (dragIndex === hoverIndex) {
|
return
|
}
|
|
props.moveRow(dragIndex, hoverIndex)
|
|
monitor.getItem().index = hoverIndex
|
},
|
}
|
|
const DragableBodyRow = DropTarget('row', rowTarget, (connect, monitor) => ({
|
connectDropTarget: connect.dropTarget(),
|
isOver: monitor.isOver(),
|
}))(
|
DragSource('row', rowSource, connect => ({
|
connectDragSource: connect.dragSource(),
|
}))(BodyRow),
|
)
|
|
class EditableCell extends Component {
|
getInput = (form) => {
|
const { inputType, options, min, max, unlimit } = this.props
|
|
if (inputType === 'number' && unlimit) {
|
return <InputNumber onPressEnter={() => this.getValue(form)} />
|
} else if (inputType === 'number') {
|
return <InputNumber min={min} max={max} precision={0} onPressEnter={() => this.getValue(form)} />
|
} else if (inputType === 'color') {
|
return <ColorSketch />
|
} else if (inputType === 'icon') {
|
return <MkEditIcon allowClear/>
|
} else if (inputType === 'switch') {
|
return <CusSwitch />
|
} else if (inputType === 'select') {
|
return (
|
<Select>
|
{options.map((item, i) => (<Select.Option key={i} value={item.field || item.value}> {item.label || item.text} </Select.Option>))}
|
</Select>
|
)
|
} else if (inputType === 'multiStr') {
|
return (
|
<Select mode="multiple">
|
{options.map((item, i) => (<Select.Option key={i} value={item.field || item.value}> {item.label || item.text} </Select.Option>))}
|
</Select>
|
)
|
} else if (inputType === 'cascader') {
|
return (
|
<Cascader options={options} placeholder=""/>
|
)
|
} else if (inputType === 'radio') {
|
return (
|
<Radio.Group style={{whiteSpace: 'nowrap'}}>
|
{options.map((item, i) => (<Radio key={i} value={item.field || item.value}> {item.label || item.text} </Radio>))}
|
</Radio.Group>
|
)
|
} else {
|
return <Input onPressEnter={() => this.getValue(form)}/>
|
}
|
}
|
|
getValue = (form) => {
|
const { record } = this.props
|
form.validateFields((error, row) => {
|
if (error) {
|
return
|
}
|
this.props.onSave({...record, ...row})
|
})
|
}
|
|
renderCell = (form) => {
|
const { getFieldDecorator } = form
|
const { editing, dataIndex, title, record, children, className, required, inputType } = this.props
|
|
return (
|
<td className={className}>
|
{editing ? (
|
<Form.Item style={{ margin: 0 }}>
|
{getFieldDecorator(dataIndex, {
|
rules: [
|
{
|
required: required,
|
message: ['number', 'text', 'input'].includes(inputType) ? `${eTDict['form.required.input']} ${title}!` : `${eTDict['form.required.select']} ${title}!`,
|
}
|
],
|
initialValue: inputType === 'multiStr' ? (record[dataIndex] ? record[dataIndex].split(',') : []) : record[dataIndex],
|
})(this.getInput(form))}
|
</Form.Item>
|
) : (
|
children
|
)}
|
</td>
|
)
|
}
|
|
render() {
|
return <EditableContext.Consumer>{this.renderCell}</EditableContext.Consumer>
|
}
|
}
|
|
class EditTable extends Component {
|
static propTpyes = {
|
actions: PropTypes.any, // 操作项
|
data: PropTypes.any, // 数据列表
|
columns: PropTypes.array, // 显示列
|
onChange: PropTypes.func // 数据变化
|
}
|
|
state = {
|
data: [],
|
editingKey: '',
|
visible: false,
|
editLineId: '',
|
columns: []
|
}
|
|
UNSAFE_componentWillMount () {
|
const { data, actions } = this.props
|
let columns = fromJS(this.props.columns).toJS()
|
let operation = null
|
|
if (actions && (actions.includes('edit') || actions.includes('copy') || actions.includes('del'))) {
|
let _operation = null
|
columns = columns.filter(item => {
|
if (item.dataIndex === 'operation') {
|
_operation = item
|
}
|
return item.dataIndex !== 'operation'
|
})
|
|
operation = {
|
title: (<div>
|
{eTDict['model.operation']}
|
<span className="copy-control">
|
{actions.includes('copy') ? <CopyOutlined title="复制" onClick={() => this.copy()} /> : null}
|
{actions.includes('copy') ? <SnippetsOutlined title="粘贴" onClick={this.paste} /> : null}
|
{actions.includes('clear') ? <DeleteOutlined title="清空" onClick={this.clear} /> : null}
|
</span>
|
</div>),
|
dataIndex: 'operation',
|
width: '140px',
|
render: (text, record) => {
|
const { editingKey } = this.state
|
const editable = this.isEditing(record)
|
return editable ? (
|
<div style={{textAlign: 'center', minWidth: '110px'}}>
|
<EditableContext.Consumer>
|
{form => (
|
<span onClick={() => this.save(form, record.uuid)} style={{ marginRight: 8 , color: '#1890ff', cursor: 'pointer'}}>
|
{eTDict['model.save']}
|
</span>
|
)}
|
</EditableContext.Consumer>
|
<span style={{ color: '#1890ff', cursor: 'pointer'}} onClick={() => this.cancel(record.uuid)}>{eTDict['model.cancel']}</span>
|
</div>
|
) : (
|
<div className={'edit-operation-btn' + (editingKey !== '' ? ' disabled' : '')} style={{minWidth: '110px', whiteSpace: 'nowrap'}}>
|
{actions.includes('edit') ? <span className="primary" onClick={() => {editingKey === '' && this.edit(record.uuid)}}><EditOutlined /></span> : null}
|
{actions.includes('copy') ? <span className="copy" onClick={() => {editingKey === '' && this.copy(record)}}><CopyOutlined /></span> : null}
|
{actions.includes('del') && editingKey === '' ? <Popconfirm
|
overlayClassName="popover-confirm"
|
title={eTDict['model.query.delete']}
|
onConfirm={() => this.handleDelete(record.uuid)
|
}>
|
<span className="danger"><DeleteOutlined /></span>
|
</Popconfirm> : null}
|
{actions.includes('del') && editingKey !== '' ? <span className="danger"><DeleteOutlined /></span> : null}
|
</div>
|
)
|
}
|
}
|
|
if (_operation) {
|
operation.render = _operation.render
|
operation.width = _operation.width
|
}
|
columns.push(operation)
|
}
|
|
this.setState({
|
data: data || [],
|
operation,
|
columns
|
})
|
}
|
|
UNSAFE_componentWillReceiveProps (nextProps) {
|
if (!is(fromJS(this.state.data), fromJS(nextProps.data))) {
|
this.setState({data: nextProps.data, editingKey: ''})
|
} else if (!is(fromJS(this.props.columns), fromJS(nextProps.columns))) {
|
if (nextProps.columns.length === this.props.columns.length) {
|
let cols = {}
|
nextProps.columns.forEach(col => {cols[col.dataIndex] = col})
|
|
this.setState({
|
columns: this.state.columns.map(col => {
|
if (cols[col.dataIndex]) {
|
return cols[col.dataIndex]
|
}
|
return col
|
})
|
})
|
} else {
|
let columns = fromJS(nextProps.columns).toJS()
|
if (this.state.operation) {
|
columns.push(this.state.operation)
|
}
|
this.setState({columns})
|
}
|
}
|
}
|
|
componentDidMount () {
|
MKEmitter.addListener('editLineId', this.getEditLineId)
|
}
|
|
componentWillUnmount () {
|
this.setState = () => {
|
return
|
}
|
MKEmitter.removeListener('editLineId', this.getEditLineId)
|
}
|
|
getEditLineId = (id) => {
|
this.setState({ editLineId: id })
|
}
|
|
isEditing = record => record.uuid === this.state.editingKey
|
|
cancel = () => {
|
this.setState({ editingKey: '' })
|
}
|
|
clear = () => {
|
const _this = this
|
|
confirm({
|
title: '确定清空列表吗?',
|
content: '',
|
onOk() {
|
_this.setState({ data: [], editingKey: '' }, () => {
|
_this.props.onChange([])
|
})
|
},
|
onCancel() {}
|
})
|
}
|
|
copy = (item) => {
|
const { type } = this.props
|
const { data } = this.state
|
|
if (!data || data.length === 0) {
|
message.warning('未获取到配置信息')
|
return
|
}
|
|
let msg = { key: type }
|
|
if (item) {
|
msg.type = 'line'
|
msg.data = item
|
} else {
|
msg.type = 'array'
|
msg.data = data
|
}
|
|
try {
|
msg = window.btoa(window.encodeURIComponent(JSON.stringify(msg)))
|
} catch (e) {
|
console.warn('Stringify Failure')
|
msg = ''
|
}
|
|
if (msg) {
|
let oInput = document.createElement('input')
|
oInput.value = msg
|
document.body.appendChild(oInput)
|
oInput.select()
|
document.execCommand('Copy')
|
document.body.removeChild(oInput)
|
message.success('复制成功。')
|
}
|
}
|
|
paste = () => {
|
this.setState({visible: true})
|
}
|
|
pasteSubmit = () => {
|
const { type } = this.props
|
const { columns } = this.state
|
let data = fromJS(this.state.data).toJS()
|
|
this.pasteFormRef.handleConfirm().then(res => {
|
if (res.key !== type) {
|
message.warning('配置信息格式错误!')
|
return
|
}
|
|
if (res.type === 'line') {
|
let unique = true
|
res.data.uuid = Utils.getuuid()
|
columns.forEach(col => {
|
if (col.unique !== true || !unique) return
|
|
let _index = data.findIndex(item => res.data[col.dataIndex] === item[col.dataIndex])
|
|
if (_index > -1) {
|
notification.warning({
|
top: 92,
|
message: col.title + '不可重复!',
|
duration: 5
|
})
|
unique = false
|
}
|
})
|
|
if (!unique) return
|
|
data.unshift(res.data)
|
this.setState({ data, editingKey: '', visible: false }, () => {
|
this.props.onChange(data)
|
})
|
} else if (res.type === 'array') {
|
res.data.forEach(cell => {
|
let unique = true
|
cell.uuid = Utils.getuuid()
|
columns.forEach(col => {
|
if (col.unique !== true || !unique) return
|
let _index = data.findIndex(item => cell[col.dataIndex] === item[col.dataIndex])
|
|
if (_index > -1) {
|
unique = false
|
}
|
})
|
|
if (!unique) return
|
|
data.push(cell)
|
})
|
|
this.setState({ data, editingKey: '', visible: false }, () => {
|
this.props.onChange(data)
|
})
|
}
|
message.success('粘贴成功。')
|
})
|
}
|
|
onSave = (record) => {
|
const { columns } = this.state
|
const newData = [...this.state.data]
|
const index = newData.findIndex(item => record.uuid === item.uuid)
|
|
if (index === -1) {
|
notification.warning({
|
top: 92,
|
message: '数据错误,无法找到行ID!',
|
duration: 5
|
})
|
return
|
}
|
|
let unique = true
|
columns.forEach(col => {
|
if (col.unique !== true || !unique) return
|
|
let _index = newData.findIndex(item => record.uuid !== item.uuid && record[col.dataIndex] === item[col.dataIndex])
|
|
if (_index > -1) {
|
notification.warning({
|
top: 92,
|
message: col.title + '不可重复!',
|
duration: 5
|
})
|
unique = false
|
}
|
})
|
|
if (!unique) return
|
|
newData.splice(index, 1, record)
|
this.setState({ data: newData, editingKey: '' }, () => {
|
this.props.onChange(newData)
|
})
|
}
|
|
handleDelete = (uuid) => {
|
const { data } = this.state
|
let _data = data.filter(item => uuid !== item.uuid)
|
|
this.setState({
|
data: _data
|
}, () => {
|
this.props.onChange(_data)
|
})
|
}
|
|
save(form, uuid) {
|
const { columns } = this.state
|
form.validateFields((error, row) => {
|
if (error) {
|
return;
|
}
|
const newData = [...this.state.data]
|
const index = newData.findIndex(item => uuid === item.uuid)
|
|
let unique = true
|
columns.forEach(col => {
|
if (col.unique !== true || !unique) return
|
|
let _index = newData.findIndex(item => uuid !== item.uuid && row[col.dataIndex] === item[col.dataIndex])
|
|
if (_index > -1) {
|
notification.warning({
|
top: 92,
|
message: col.title + '不可重复!',
|
duration: 5
|
})
|
unique = false
|
}
|
})
|
|
if (!unique) return
|
|
if (index > -1) {
|
const item = newData[index]
|
newData.splice(index, 1, {
|
...item,
|
...row,
|
})
|
this.setState({ data: newData, editingKey: '' }, () => {
|
this.props.onChange(newData)
|
})
|
} else {
|
newData.push(row);
|
this.setState({ data: newData, editingKey: '' }, () => {
|
this.props.onChange(newData)
|
})
|
}
|
})
|
}
|
|
edit(uuid) {
|
this.setState({ editingKey: uuid })
|
}
|
|
moveRow = (dragIndex, hoverIndex) => {
|
const { editingKey } = this.state
|
let _data = fromJS(this.state.data).toJS()
|
|
if (editingKey) return
|
|
_data.splice(hoverIndex, 0, ..._data.splice(dragIndex, 1))
|
|
this.setState({
|
data: _data
|
}, () => {
|
this.props.onChange(_data)
|
})
|
}
|
|
render() {
|
const { actions, indexShow } = this.props
|
const { editLineId } = this.state
|
|
let components = {
|
body: {
|
cell: EditableCell
|
}
|
}
|
|
let moveprops = {}
|
if (actions.includes('move')) {
|
components.body.row = DragableBodyRow
|
moveprops.moveAble = !this.state.editingKey
|
moveprops.moveRow = this.moveRow
|
}
|
|
let columns = this.state.columns.map(col => {
|
if (col.copy) {
|
col.render = (text) => (<Paragraph copyable>{text}</Paragraph>)
|
}
|
if (!col.editable) return col
|
return {
|
...col,
|
onCell: record => ({
|
record,
|
inputType: col.inputType,
|
dataIndex: col.dataIndex,
|
options: col.options || [],
|
min: col.min || 0,
|
max: col.max || 500,
|
unlimit: col.unlimit,
|
required: col.required !== false ? true : false,
|
title: col.title,
|
editing: this.isEditing(record),
|
onSave: this.onSave,
|
}),
|
}
|
})
|
|
if (indexShow !== false) {
|
columns.unshift({
|
title: '序号',
|
dataIndex: '$index',
|
className: 'mk-index',
|
width: '60px',
|
})
|
}
|
|
const data = this.state.data.map((item, index) => {
|
item.$index = index + 1
|
|
return item
|
})
|
|
return (
|
<EditableContext.Provider value={this.props.form}>
|
<div className="modal-edit-table">
|
<DndProvider>
|
<Table
|
bordered
|
rowKey="uuid"
|
components={components}
|
dataSource={data}
|
columns={columns}
|
rowClassName={record => !editLineId || editLineId !== record.uuid ? 'editable-row' : 'editable-row active'}
|
pagination={false}
|
onRow={(record, index) => ({
|
index,
|
...moveprops
|
})}
|
/>
|
</DndProvider>
|
{/* 信息粘贴 */}
|
<Modal
|
title={eTDict['header.form.paste']}
|
visible={this.state.visible}
|
width={600}
|
maskClosable={false}
|
onOk={this.pasteSubmit}
|
onCancel={() => {this.setState({visible: false})}}
|
destroyOnClose
|
>
|
<PasteForm dict={eTDict} wrappedComponentRef={(inst) => this.pasteFormRef = inst}/>
|
</Modal>
|
</div>
|
</EditableContext.Provider>
|
)
|
}
|
}
|
|
export default Form.create()(EditTable)
|