| | |
| | | import React, {Component} from 'react' |
| | | import PropTypes from 'prop-types' |
| | | import { is, fromJS } from 'immutable' |
| | | import { Table, Input, Popconfirm, Form, message } from 'antd' |
| | | import { ArrowUpOutlined, ArrowDownOutlined, DeleteOutlined, PlusOutlined, SwapOutlined } from '@ant-design/icons' |
| | | import { DndProvider, DragSource, DropTarget } from 'react-dnd' |
| | | import { Table, Input, Popconfirm, message } from 'antd' |
| | | import { DeleteOutlined, PlusOutlined, SwapOutlined, DragOutlined } from '@ant-design/icons' |
| | | |
| | | import Utils from '@/utils/utils.js' |
| | | import asyncComponent from '@/utils/asyncComponent' |
| | | import './index.scss' |
| | | |
| | | const EditableContext = React.createContext() |
| | | const SourceComponent = asyncComponent(() => import('@/menu/components/share/sourcecomponent')) |
| | | |
| | | const EditableRow = ({ form, index, ...props }) => ( |
| | | <EditableContext.Provider value={form}> |
| | | <tr {...props} /> |
| | | </EditableContext.Provider> |
| | | class MoveTd extends React.Component { |
| | | render() { |
| | | const { connectDragSource, connectDropTarget } = this.props |
| | | |
| | | return connectDragSource( |
| | | connectDropTarget(<td className="mk-move-col"><DragOutlined /></td>), |
| | | ) |
| | | } |
| | | } |
| | | |
| | | const rowSource = { |
| | | beginDrag(props) { |
| | | 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 DragableTd = DropTarget('td', rowTarget, connect => ({ |
| | | connectDropTarget: connect.dropTarget(), |
| | | }))( |
| | | DragSource('td', rowSource, (connect, monitor) => ({ |
| | | connectDragSource: connect.dragSource(), |
| | | // isDragging: monitor.isDragging() |
| | | }))(MoveTd), |
| | | ) |
| | | |
| | | const EditableFormRow = Form.create()(EditableRow) |
| | | |
| | | class EditableCell extends Component { |
| | | state = { |
| | | editing: false |
| | | editing: false, |
| | | value: '' |
| | | } |
| | | |
| | | toggleEdit = () => { |
| | | const editing = !this.state.editing |
| | | this.setState({ editing }, () => { |
| | | if (editing && this.input && this.input.select) { |
| | | trigger = () => { |
| | | const { dataIndex, record } = this.props |
| | | |
| | | this.setState({ editing: true, value: record[dataIndex] }, () => { |
| | | if (this.input && this.input.select) { |
| | | this.input.select() |
| | | } else if (editing && this.input && this.input.focus) { |
| | | } else if (this.input && this.input.focus) { |
| | | this.input.focus() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | save = e => { |
| | | const { record, handleSave } = this.props |
| | | this.form.validateFields((error, values) => { |
| | | handleSave({ ...record, ...values }) |
| | | if (error && error[e.currentTarget.id]) { |
| | | return |
| | | } |
| | | this.toggleEdit() |
| | | }) |
| | | save = () => { |
| | | const { record, handleSave, dataIndex } = this.props |
| | | const { value } = this.state |
| | | |
| | | handleSave({ ...record, [dataIndex]: value }) |
| | | |
| | | this.setState({ editing: false, value: '' }) |
| | | } |
| | | |
| | | renderCell = form => { |
| | | this.form = form |
| | | const { children, dataIndex, record } = this.props |
| | | changeUrl = (val) => { |
| | | const { record, handleSave, dataIndex } = this.props |
| | | |
| | | handleSave({ ...record, [dataIndex]: val }) |
| | | } |
| | | |
| | | renderCell = () => { |
| | | const { dataIndex, inputType, record } = this.props |
| | | const { editing } = this.state |
| | | |
| | | return editing ? ( |
| | | <Form.Item style={{ margin: '0 -5px 0 -5px' }}> |
| | | {form.getFieldDecorator(dataIndex, { |
| | | rules: [ |
| | | { |
| | | required: dataIndex === 'Text', |
| | | message: '不可为空.', |
| | | } |
| | | ], |
| | | 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> |
| | | ) |
| | | if (inputType === 'file') { |
| | | return <SourceComponent initialValue={record[dataIndex]} type="" onChange={this.changeUrl} placement="right"/> |
| | | } |
| | | |
| | | if (!editing) { |
| | | return ( |
| | | <div |
| | | className="editable-cell-value-wrap" |
| | | onClick={this.trigger} |
| | | > |
| | | {record[dataIndex]} |
| | | </div> |
| | | ) |
| | | } else { |
| | | return <Input ref={node => (this.input = node)} defaultValue={record[dataIndex]} autoComplete="off" onChange={(e) => this.setState({value: e.target.value})} onPressEnter={this.save} onBlur={this.save} /> |
| | | } |
| | | } |
| | | |
| | | render() { |
| | | const { |
| | | editable, |
| | | dataIndex, |
| | | title, |
| | | record, |
| | | index, |
| | | handleSave, |
| | | children, |
| | | ...restProps |
| | | } = this.props |
| | | const { editable, dataIndex, index } = this.props |
| | | |
| | | if (dataIndex === '$move') { |
| | | return (<DragableTd key={index} {...this.props} />) |
| | | } |
| | | |
| | | if (editable) { |
| | | return ( |
| | | <td>{this.renderCell()}</td> |
| | | ) |
| | | } |
| | | |
| | | return ( |
| | | <td {...restProps}> |
| | | {editable ? ( |
| | | <EditableContext.Consumer style={{padding: 0}}>{this.renderCell}</EditableContext.Consumer> |
| | | ) : ( |
| | | children |
| | | )} |
| | | </td> |
| | | <td {...this.props}/> |
| | | ) |
| | | } |
| | | } |
| | | |
| | | class EditTable extends Component { |
| | | static propTpyes = { |
| | | type: PropTypes.string, // 表单类型 |
| | | module: PropTypes.string, // 元素类型 |
| | | linkSubFields: PropTypes.array, // 关联字段 |
| | | transfield: PropTypes.object, // 表单字段名称 |
| | | onChange: PropTypes.func // 数据变化 |
| | | type: PropTypes.any, |
| | | module: PropTypes.string, |
| | | columns: PropTypes.array, |
| | | onChange: PropTypes.func |
| | | } |
| | | |
| | | state = { |
| | |
| | | } |
| | | |
| | | UNSAFE_componentWillMount () { |
| | | const { linkSubFields, type } = this.props |
| | | let data = this.props['data-__meta'].initialValue || [] |
| | | |
| | | const { columns } = this.getColumns(type, linkSubFields, data) |
| | | const { columns, value } = this.props |
| | | let data = value || [] |
| | | |
| | | this.setState({ |
| | | columns: columns, |
| | | dataSource: data, |
| | | columns: this.getColumns(), |
| | | dataSource: data.map(item => { |
| | | columns.forEach(n => { |
| | | if (item[n.key] !== undefined) return |
| | | item[n.key] = ['ParentID', '$url', '$color', '$value'].includes(n.key) ? '' : item.Text || '' |
| | | }) |
| | | return item |
| | | }), |
| | | count: data.length |
| | | }) |
| | | } |
| | | |
| | | handleUpDown = (record, direction) => { |
| | | moveRow = (dragId, hoverId) => { |
| | | const { dataSource } = this.state |
| | | let index = 0 |
| | | |
| | | let _data = dataSource.filter((item, i) => { |
| | | if (item.key === record.key) { |
| | | index = i |
| | | let dragIndex = -1 |
| | | let hoverIndex = -1 |
| | | |
| | | dataSource.forEach((item, i) => { |
| | | if (item.key === dragId) { |
| | | dragIndex = i |
| | | } else if (item.key === hoverId) { |
| | | hoverIndex = i |
| | | } |
| | | |
| | | return item.key !== record.key |
| | | }) |
| | | if ((index === 0 && direction === 'up') || (index === dataSource.length - 1 && direction === 'down')) { |
| | | return |
| | | } |
| | | |
| | | if (dragIndex === -1 || hoverIndex === -1) return |
| | | |
| | | if (direction === 'up') { |
| | | _data.splice(index - 1, 0, record) |
| | | } else { |
| | | _data.splice(index + 1, 0, record) |
| | | } |
| | | let _data = fromJS(dataSource).toJS() |
| | | |
| | | _data.splice(hoverIndex, 0, ..._data.splice(dragIndex, 1)) |
| | | |
| | | this.setState({ |
| | | dataSource: _data |
| | |
| | | }) |
| | | } |
| | | |
| | | handleHide = (record) => { |
| | | handleHide = (key) => { |
| | | let _data = this.state.dataSource.map(item => { |
| | | if (item.key === record.key) { |
| | | if (item.key === key) { |
| | | item.Hide = !item.Hide |
| | | } |
| | | return item |
| | | }) |
| | | |
| | | this.setState({ |
| | | dataSource: _data |
| | | }, () => { |
| | |
| | | }) |
| | | } |
| | | |
| | | handleDelete = key => { |
| | | handleDelete = (key) => { |
| | | const { dataSource } = this.state |
| | | let _data = dataSource.filter(item => item.key !== key) |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | handleAdd = (e) => { |
| | | e.stopPropagation() |
| | | const { linkSubFields } = this.props |
| | | handleAdd = () => { |
| | | const { columns } = this.props |
| | | const { count, dataSource } = this.state |
| | | const newData = { |
| | | key: Utils.getuuid(), |
| | | Value: `${count}`, |
| | | Text: `${count}`, |
| | | ParentID: '' |
| | | } |
| | | |
| | | linkSubFields.forEach(m => { |
| | | newData[m] = newData[m] || '' |
| | | let item = { key: Utils.getuuid() } |
| | | |
| | | columns.forEach(m => { |
| | | item[m.key] = '' |
| | | }) |
| | | |
| | | let _data = [...dataSource, newData] |
| | | if (item.Value === '') { |
| | | item.Value = `${count + 1}` |
| | | } |
| | | if (item.$value === '') { |
| | | item.$value = `${count + 1}` |
| | | } |
| | | |
| | | item.Text = `${count + 1}` |
| | | |
| | | let _data = [...dataSource, item] |
| | | |
| | | this.setState({ |
| | | dataSource: _data, |
| | |
| | | } |
| | | |
| | | handleSave = row => { |
| | | const { type } = this.props |
| | | const { columns, type } = this.props |
| | | const newData = [...this.state.dataSource] |
| | | const index = newData.findIndex(item => row.key === item.key) |
| | | const item = newData[index] |
| | | |
| | | if (type === 'link') { |
| | | if (newData.filter(m => row.key !== m.key && row.Value === m.Value && row.ParentID === m.ParentID).length > 0) { |
| | | message.warning('相同ParentID下,此Value值已存在!') |
| | | } |
| | | if (type === 'proc') { |
| | | // if (!row.origin || /^\s+$/.test(row.origin)) { |
| | | // message.warning(columns[0].title + '为空时无效!') |
| | | // } |
| | | } else { |
| | | if (newData.filter(m => row.key !== m.key && row.Value === m.Value).length > 0) { |
| | | message.warning('此Value值已存在!') |
| | | let val = '' |
| | | let repeat = false |
| | | let _type = '' |
| | | columns.forEach(col => { |
| | | if (!col.strict) return |
| | | |
| | | if (col.key === 'ParentID') { |
| | | _type = 'mutil' |
| | | } |
| | | |
| | | val += row[col.key] |
| | | }) |
| | | |
| | | newData.forEach(item => { |
| | | if (row.key === item.key) return |
| | | |
| | | let _val = '' |
| | | columns.forEach(col => { |
| | | if (!col.strict) return |
| | | |
| | | _val += item[col.key] |
| | | }) |
| | | |
| | | if (val === _val) { |
| | | repeat = true |
| | | } |
| | | }) |
| | | if (repeat) { |
| | | if (_type === 'mutil') { |
| | | message.warning('相同ParentID下,此Value值已存在!') |
| | | } else { |
| | | message.warning('此Value值已存在!') |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | }) |
| | | } |
| | | |
| | | getColumns = (type, linkSubFields, dataSource) => { |
| | | const { transfield } = this.props |
| | | getColumns = () => { |
| | | const { columns } = this.props |
| | | |
| | | let _dataSource = fromJS(dataSource).toJS() |
| | | let fields = [] |
| | | let subFields = linkSubFields.filter(m => m !== 'Value' && m !== 'Text') |
| | | |
| | | if (subFields.length > 0) { |
| | | _dataSource = _dataSource.map(data => { |
| | | subFields.forEach(n => { |
| | | if (data[n] !== undefined) return |
| | | data[n] = data.Text || '' |
| | | }) |
| | | return data |
| | | let fields = [{ |
| | | title: ' ', |
| | | width: '60px', |
| | | dataIndex: '$move', |
| | | onCell: (record) => ({ |
| | | index: record.key, |
| | | dataIndex: '$move', |
| | | moveRow: this.moveRow |
| | | }) |
| | | |
| | | fields = subFields.map(field => { |
| | | return { |
| | | title: transfield[field] || field, |
| | | $title: transfield[field] || field, |
| | | dataIndex: field, |
| | | }] |
| | | columns.forEach(n => { |
| | | let col = { |
| | | title: n.title, |
| | | dataIndex: n.key, |
| | | onCell: record => ({ |
| | | record, |
| | | editable: true, |
| | | } |
| | | }) |
| | | } |
| | | |
| | | let columns = [ |
| | | { |
| | | title: 'Value', |
| | | $title: 'Value', |
| | | dataIndex: 'Value', |
| | | editable: true |
| | | }, |
| | | { |
| | | title: 'Text', |
| | | $title: 'Text', |
| | | dataIndex: 'Text', |
| | | editable: true |
| | | }, |
| | | ...fields, |
| | | { |
| | | title: '操作', |
| | | align: 'center', |
| | | width: '20%', |
| | | dataIndex: 'operation', |
| | | render: (text, record) => |
| | | this.state.dataSource.length >= 1 ? ( |
| | | <div style={{fontSize: '15px'}}> |
| | | <span className="operation-btn" onClick={() => this.handleUpDown(record, 'up')} style={{color: '#1890ff'}}><ArrowUpOutlined /></span> |
| | | <span className="operation-btn" onClick={() => this.handleUpDown(record, 'down')} style={{color: '#ff4d4f'}}><ArrowDownOutlined /></span> |
| | | <span className="operation-btn" title="显示/隐藏" onClick={() => this.handleHide(record)} style={{color: 'rgb(142, 68, 173)'}}><SwapOutlined /></span> |
| | | <Popconfirm |
| | | title="确定删除吗?" |
| | | overlayClassName="popover-confirm" |
| | | onConfirm={() => this.handleDelete(record.key) |
| | | }> |
| | | <span style={{color: '#ff4d4f', cursor: 'pointer'}}><DeleteOutlined /></span> |
| | | </Popconfirm> |
| | | </div> |
| | | ) : null, |
| | | inputType: n.type || 'text', |
| | | dataIndex: n.key, |
| | | handleSave: this.handleSave |
| | | }) |
| | | } |
| | | ] |
| | | |
| | | if (type === 'link') { |
| | | columns.unshift({ |
| | | title: 'ParentID', |
| | | $title: 'ParentID', |
| | | dataIndex: 'ParentID', |
| | | editable: true |
| | | }) |
| | | } |
| | | if (n.width) { |
| | | col.width = n.width |
| | | } |
| | | if (n.fixed) { |
| | | delete col.onCell |
| | | } |
| | | |
| | | return { |
| | | columns: columns.map(col => { |
| | | if (col.dataIndex !== 'operation') { |
| | | col.title = <div> |
| | | {col.$title} |
| | | fields.push(col) |
| | | }) |
| | | |
| | | fields.push({ |
| | | title: '操作', |
| | | align: 'center', |
| | | width: '110px', |
| | | dataIndex: 'operation', |
| | | render: (text, record) => |
| | | ( |
| | | <div style={{fontSize: '15px'}}> |
| | | <span className="operation-btn" title="显示/隐藏" onClick={() => this.handleHide(record.key)} style={{color: 'rgb(142, 68, 173)'}}><SwapOutlined /></span> |
| | | <Popconfirm |
| | | title="确定删除吗?" |
| | | overlayClassName="popover-confirm" |
| | | onConfirm={() => this.handleDelete(record.key) |
| | | }> |
| | | <span style={{color: '#ff4d4f', cursor: 'pointer'}}><DeleteOutlined /></span> |
| | | </Popconfirm> |
| | | </div> |
| | | } |
| | | return col |
| | | }), |
| | | dataSource: _dataSource |
| | | } |
| | | ) |
| | | }) |
| | | |
| | | return fields |
| | | } |
| | | |
| | | handleEmpty = (e) => { |
| | | e.stopPropagation() |
| | | const { linkSubFields, module } = this.props |
| | | handleEmpty = () => { |
| | | const { columns, module } = this.props |
| | | const { dataSource } = this.state |
| | | |
| | | if (dataSource.filter(item => item.Value === '').length > 0) { |
| | | message.warning('Value为空已存在!') |
| | | return |
| | | } |
| | | const newData = { |
| | | key: Utils.getuuid(), |
| | | Value: '', |
| | | Text: module === 'form' ? '空' : '全部', |
| | | ParentID: '' |
| | | } |
| | | |
| | | linkSubFields.forEach(m => { |
| | | newData[m] = newData[m] || '' |
| | | let item = { key: Utils.getuuid() } |
| | | |
| | | columns.forEach(m => { |
| | | item[m.key] = '' |
| | | }) |
| | | |
| | | let _data = [newData, ...dataSource] |
| | | item.Text = module === 'form' ? '空' : '全部' |
| | | |
| | | let _data = [item, ...dataSource] |
| | | |
| | | this.setState({ |
| | | dataSource: _data, |
| | |
| | | }) |
| | | } |
| | | |
| | | resetColumn = (type, linkSubFields) => { |
| | | const { columns, dataSource } = this.getColumns(type, linkSubFields, this.state.dataSource) |
| | | resetColumn = () => { |
| | | const { columns, value } = this.props |
| | | |
| | | let data = fromJS(value).toJS().map(item => { |
| | | columns.forEach(n => { |
| | | if (item[n.key] !== undefined) return |
| | | item[n.key] = ['ParentID', '$url', '$color', '$value'].includes(n.key) ? '' : item.Text || '' |
| | | }) |
| | | return item |
| | | }) |
| | | |
| | | if (!is(fromJS(dataSource), fromJS(this.state.dataSource))) { |
| | | this.setState({ |
| | | columns, |
| | | dataSource |
| | | }, () => { |
| | | this.props.onChange(dataSource) |
| | | }) |
| | | } else { |
| | | this.setState({ |
| | | columns |
| | | }) |
| | | } |
| | | this.setState({ |
| | | columns: this.getColumns(), |
| | | dataSource: data, |
| | | count: data.length |
| | | }, () => { |
| | | this.props.onChange(data) |
| | | }) |
| | | } |
| | | |
| | | UNSAFE_componentWillReceiveProps (nextProps) { |
| | | if (!is(fromJS(this.props.linkSubFields), fromJS(nextProps.linkSubFields)) || this.props.type !== nextProps.type) { |
| | | this.resetColumn(nextProps.type, nextProps.linkSubFields) |
| | | if (!is(fromJS(this.props.columns), fromJS(nextProps.columns))) { |
| | | this.setState({}, () => { |
| | | this.resetColumn() |
| | | }) |
| | | } |
| | | } |
| | | |
| | | render() { |
| | | const { module } = this.props |
| | | const { dataSource } = this.state |
| | | const { dataSource, columns } = 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, |
| | | handleSave: this.handleSave |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | return ( |
| | | <div className="common-modal-edit-table"> |
| | | <span className="add-row add-row-empty" onClick={this.handleEmpty}>{module === 'form' ? '空' : '全部'}</span> |
| | | {module ? <span className="add-row add-row-empty" onClick={this.handleEmpty}>{module === 'form' ? '空' : '全部'}</span> : null} |
| | | <PlusOutlined className="add-row" onClick={this.handleAdd} /> |
| | | <Table |
| | | components={components} |
| | | rowClassName={(record) => record.Hide ? 'editable-row hide' : 'editable-row'} |
| | | bordered |
| | | dataSource={dataSource} |
| | | columns={columns} |
| | | pagination={false} |
| | | /> |
| | | <DndProvider> |
| | | <Table |
| | | components={components} |
| | | rowClassName={(record) => record.Hide ? 'editable-row hide' : 'editable-row'} |
| | | bordered |
| | | dataSource={dataSource} |
| | | columns={columns} |
| | | pagination={false} |
| | | /> |
| | | </DndProvider> |
| | | </div> |
| | | ) |
| | | } |