king
2021-09-01 31ec63f0419895876cbaba99637a884a32d33d0d
src/templates/zshare/editTable/index.jsx
@@ -1,30 +1,116 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Table, Input, InputNumber, Popconfirm, Form, Icon, Select } from 'antd'
import { DndProvider, DragSource, DropTarget } from 'react-dnd'
import { Table, Input, InputNumber, Popconfirm, Form, Icon, Select, Radio, Cascader, notification, message, Modal, Typography } from 'antd'
import Utils from '@/utils/utils.js'
import ColorSketch from '@/mob/colorsketch'
// import Utils from '@/utils/utils.js'
import PasteForm from '@/templates/zshare/pasteform'
import CusSwitch from './cusSwitch'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
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 } = this.props
    const { inputType, options, min, max, unlimit } = this.props
    if (inputType === 'number') {
      return <InputNumber min={12} max={50} precision={0} onPressEnter={() => this.getValue(form)} />
    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 === '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>
      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>
          {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)} />
      return <Input onPressEnter={() => this.getValue(form)}/>
    }
  }
@@ -40,7 +126,7 @@
  renderCell = (form) => {
    const { getFieldDecorator } = form
    const { editing, dataIndex, title, record, children, className, required } = this.props
    const { editing, dataIndex, title, record, children, className, required, inputType } = this.props
    return (
      <td className={className}>
@@ -50,10 +136,10 @@
              rules: [
                {
                  required: required,
                  message: `Please Input ${title}!`,
                  message: ['number', 'text', 'input'].includes(inputType) ? `${eTDict['form.required.input']} ${title}!` : `${eTDict['form.required.select']} ${title}!`,
                }
              ],
              initialValue: record[dataIndex],
              initialValue: inputType === 'multiStr' ? (record[dataIndex] ? record[dataIndex].split(',') : []) : record[dataIndex],
            })(this.getInput(form))}
          </Form.Item>
        ) : (
@@ -70,66 +156,112 @@
class EditTable extends Component {
  static propTpyes = {
    actions: PropTypes.any,         // 操作项
    data: PropTypes.any,            // 数据列表
    columns: PropTypes.array,       // 显示列
    onChange: PropTypes.func        // 数据变化
  }
  state = {
    data: [],
    editingKey: '',
    visible: false,
    columns: []
  }
  UNSAFE_componentWillMount () {
    const { data } = this.props
    const { data, actions } = this.props
    let columns = fromJS(this.props.columns).toJS()
    let dict = localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
    let operation = null
    columns.push({
      title: dict['model.operation'],
      dataIndex: 'operation',
      width: '140px',
      render: (text, record) => {
        const { editingKey } = this.state
        const editable = this.isEditing(record)
        return editable ? (
          <span>
            <EditableContext.Consumer>
              {form => (
                <span onClick={() => this.save(form, record.uuid)} style={{ marginRight: 8 , color: '#1890ff', cursor: 'pointer'}}>
                  {dict['model.save']}
                </span>
              )}
            </EditableContext.Consumer>
            <span style={{ color: '#1890ff', cursor: 'pointer'}} onClick={() => this.cancel(record.uuid)}>{dict['model.cancel']}</span>
    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') ? <Icon type="copy" title="复制" onClick={() => this.copy()} /> : null}
            {actions.includes('copy') ? <Icon type="snippets" title="粘贴" onClick={this.paste} /> : null}
            {actions.includes('clear') ? <Icon type="delete" title="清空" onClick={this.clear} /> : null}
          </span>
        ) : (
          <div className={'operation-btn' + (editingKey !== '' ? ' disabled' : '')}>
            <span className="primary" onClick={() => {editingKey === '' && this.edit(record.uuid)}}><Icon type="edit" /></span>
            <span className="primary" onClick={() => {editingKey === '' && this.handleUpDown(record.uuid, 'up')}}><Icon type="arrow-up" /></span>
            <span className="danger" onClick={() => {editingKey === '' && this.handleUpDown(record.uuid, 'down')}}><Icon type="arrow-down" /></span>
            {editingKey === '' ? <Popconfirm
              overlayClassName="popover-confirm"
              title={dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record.uuid)
            }>
              <span className="danger"><Icon type="delete" /></span>
            </Popconfirm> : null}
            {editingKey !== '' ? <span className="danger"><Icon type="delete" /></span> : null}
          </div>
        )
        </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'}}>
              {actions.includes('edit') ? <span className="primary" onClick={() => {editingKey === '' && this.edit(record.uuid)}}><Icon type="edit" /></span> : null}
              {actions.includes('copy') ? <span className="copy" onClick={() => {editingKey === '' && this.copy(record)}}><Icon type="copy" /></span> : null}
              {actions.includes('del') && editingKey === '' ? <Popconfirm
                overlayClassName="popover-confirm"
                title={eTDict['model.query.delete']}
                onConfirm={() => this.handleDelete(record.uuid)
              }>
                <span className="danger"><Icon type="delete" /></span>
              </Popconfirm> : null}
              {actions.includes('del') && editingKey !== '' ? <span className="danger"><Icon type="delete" /></span> : null}
            </div>
          )
        }
      }
    })
      if (_operation) {
        operation.render = _operation.render
        operation.width = _operation.width
      }
      columns.push(operation)
    }
    this.setState({
      data: data || [],
      columns,
      dict
      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})
      }
    }
  }
@@ -139,11 +271,154 @@
    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) return
    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: '' }, () => {
@@ -162,34 +437,33 @@
    })
  }
  handleUpDown = (uuid, direction) => {
    let _data = fromJS(this.state.data).toJS()
    const index = _data.findIndex(item => uuid === item.uuid)
    if ((index === 0 && direction === 'up') || (index === _data.length - 1 && direction === 'down')) {
      return
    }
    if (direction === 'up') {
      _data.splice(index - 1, 0, ..._data.splice(index, 1))
    } else {
      _data.splice(index + 1, 0, ..._data.splice(index, 1))
    }
    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, {
@@ -212,14 +486,41 @@
    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 components = {
    const { actions } = this.props
    let components = {
      body: {
        cell: EditableCell,
        cell: EditableCell
      }
    }
    let moveprops = {}
    if (actions.includes('move')) {
      components.body.row = DragableBodyRow
      moveprops.moveAble = !this.state.editingKey
      moveprops.moveRow = this.moveRow
    }
    
    const columns = this.state.columns.map(col => {
    let  columns = this.state.columns.map(col => {
      if (col.copy) {
        col.render = (text) => (<Paragraph copyable>{text}</Paragraph>)
      }
      if (!col.editable) return col
      return {
        ...col,
@@ -228,6 +529,9 @@
          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),
@@ -236,18 +540,49 @@
      }
    })
    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">
          <Table
            bordered
            rowKey="uuid"
            components={components}
            dataSource={this.state.data}
            columns={columns}
            rowClassName="editable-row"
            pagination={false}
          />
          <DndProvider>
            <Table
              bordered
              rowKey="uuid"
              components={components}
              dataSource={data}
              columns={columns}
              rowClassName="editable-row"
              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>
    )