king
2025-04-21 f3d4db769ba9b51b799d981511a710fd443d0e08
src/templates/zshare/modalform/modaleditable/index.jsx
@@ -1,459 +1,434 @@
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 { 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, 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()
    })
  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, datatype } = 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
    let rules = []
    if (datatype === 'number') {
      rules.push({
        pattern: /^(-?\d+)(\.\d+)?$/,
        message: formRule.input.numbermsg
      })
    if (inputType === 'file') {
      return <SourceComponent initialValue={record[dataIndex]} type="" onChange={this.changeUrl} placement="right"/>
    }
    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>
    )
    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 {
  constructor(props) {
    super(props)
    let _width = '40%'
    let fields = []
    let dataItem = props.data ? props.data[0] : ''
    if (props.type === 'link') {
      _width = '27%'
    } else if (props.type === 'select') {
      _width = Math.floor(80 / (props.linkSubFields.length + 2)) + '%'
      fields = props.linkSubFields.map(field => {
        return {
          title: field.label,
          dataIndex: field.field,
          width: _width,
          editable: true,
          datatype: dataItem && typeof(dataItem[field.field]) === 'number' ? 'number' : 'string'
        }
      })
    }
    let columns = [
      {
        title: 'Value',
        dataIndex: 'Value',
        width: _width,
        editable: true,
        datatype: dataItem && typeof(dataItem.Value) === 'number' ? 'number' : 'string'
      },
      {
        title: 'Text',
        dataIndex: 'Text',
        width: _width,
        editable: true,
        datatype: dataItem && typeof(dataItem.Text) === 'number' ? 'number' : 'string'
      },
      ...fields,
      {
        title: '操作',
        align: 'center',
        dataIndex: 'operation',
        render: (text, record) =>
          this.state.dataSource.length >= 1 ? (
            <div>
              <span className="operation-btn" title={props.dict['header.form.up']} onClick={() => this.handleUpDown(record, 'up')} style={{color: '#1890ff'}}><Icon type="arrow-up" /></span>
              <span className="operation-btn" title={props.dict['header.form.down']} onClick={() => this.handleUpDown(record, 'down')} style={{color: '#ff4d4f'}}><Icon type="arrow-down" /></span>
              <Popconfirm
                title={props.dict['header.form.query.delete']}
                okText={props.dict['model.confirm']}
                cancelText={props.dict['header.cancel']}
                onConfirm={() => this.handleDelete(record.key)
              }>
                <span style={{color: '#1890ff', cursor: 'pointer'}}><Icon type="delete" /></span>
              </Popconfirm>
            </div>
          ) : null,
      }
    ]
    if (props.type === 'link') {
      columns.unshift({
        title: 'ParentID',
        dataIndex: 'ParentID',
        width: '27%',
        editable: true,
        datatype: dataItem && typeof(dataItem.ParentID) === 'number' ? 'number' : 'string'
      })
    }
    this.state = {
      columns: columns.map(col => {
        if (col.dataIndex !== 'operation') {
          col = {...col, ...this.getColumnSearchProps(col)}
        }
        return col
      }),
      dataSource: props.data,
      count: props.data.length,
      type: props.type,
      linkSubFields: props.linkSubFields
    }
  static propTpyes = {
    type: PropTypes.any,
    module: PropTypes.string,
    columns: PropTypes.array,
    onChange: PropTypes.func
  }
  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">
            String
          </Radio>
          <Radio style={{display: 'block', height: '30px', lineHeight: '30px'}} value="number">
            Number
          </Radio>
        </Radio.Group>
      </div>
    ),
    filterIcon: () => (
      <Icon type="swap" style={{ color: column.datatype === 'number' ? '#1890ff' : undefined}} />
    )
  })
  state = {
    columns: [],
    dataSource: [],
    count: 0
  }
  changeDatatype = (column, e) => {
    const { columns, dataSource } = this.state
    let value = e.target.value
  UNSAFE_componentWillMount () {
    const { columns, value } = this.props
    let data = value || []
    this.setState({
      dataSource: 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
      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
      }),
      columns: columns.map(col => {
        if (col.dataIndex === column.dataIndex) {
          col.datatype = value
        }
        if (col.dataIndex !== 'operation') {
          col = {...col, ...this.getColumnSearchProps(col)}
        }
        return col
      })
      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
    }, () => {
      this.props.onChange(_data)
    })
  }
  handleDelete = key => {
    const dataSource = [...this.state.dataSource]
    this.setState({ dataSource: dataSource.filter(item => item.key !== key) })
  handleHide = (key) => {
    let _data = this.state.dataSource.map(item => {
      if (item.key === key) {
        item.Hide = !item.Hide
      }
      return item
    })
    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}`
    const { columns } = this.props
    const { count, dataSource } = this.state
    let item = { key: Utils.getuuid() }
    columns.forEach(m => {
      item[m.key] = ''
    })
    if (item.Value === '') {
      item.Value = `${count + 1}`
    }
    if (type === 'link') {
      newData.ParentID = `${count}`
    if (item.$value === '') {
      item.$value = `${count + 1}`
    }
    item.Text = `${count + 1}`
    let _data = [...dataSource, item]
    this.setState({
      dataSource: [...dataSource, newData],
      dataSource: _data,
      count: count + 1
    }, () => {
      this.props.onChange(_data)
    })
  }
  handleSave = row => {
    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 === 'proc') {
      // if (!row.origin || /^\s+$/.test(row.origin)) {
      //   message.warning(columns[0].title + '为空时无效!')
      // }
    } else {
      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值已存在!')
        }
      }
    }
    newData.splice(index, 1, {
      ...item,
      ...row
    })
    this.setState({ dataSource: newData })
    this.setState({ dataSource: newData }, () => {
      this.props.onChange(newData)
    })
  }
  resetColumn = (type, linkSubFields) => {
    let dataSource = JSON.parse(JSON.stringify(this.state.dataSource))
    let _width = '40%'
    let fields = []
  getColumns = () => {
    const { columns } = this.props
    if (type === 'select' && linkSubFields.length > this.state.linkSubFields) {
      let addcol = linkSubFields[linkSubFields.length - 1]
      dataSource = dataSource.map(data => {
        data[addcol.field] = data.Text
        return data
    let fields = [{
      title: ' ',
      width: '60px',
      dataIndex: '$move',
      onCell: (record) => ({
        index: record.key,
        dataIndex: '$move',
        moveRow: this.moveRow
      })
    }
    let dataItem = dataSource ? dataSource[0] : ''
    if (type === 'link') {
      _width = '27%'
    } else if (type === 'select') {
      _width = Math.floor(80 / (linkSubFields.length + 2)) + '%'
      fields = linkSubFields.map(field => {
        return {
          title: field.label,
          dataIndex: field.field,
          width: _width,
    }]
    columns.forEach(n => {
      let col = {
        title: n.title,
        dataIndex: n.key,
        onCell: record => ({
          record,
          editable: true,
          datatype: dataItem && typeof(dataItem[field.field]) === 'number' ? 'number' : 'string'
        }
      })
    }
    let columns = [
      {
        title: 'Value',
        dataIndex: 'Value',
        width: _width,
        editable: true,
        datatype: dataItem && typeof(dataItem.Value) === 'number' ? 'number' : 'string'
      },
      {
        title: 'Text',
        dataIndex: 'Text',
        width: _width,
        editable: true,
        datatype: dataItem && typeof(dataItem.Text) === 'number' ? 'number' : 'string'
      },
      ...fields,
      {
        title: '操作',
        align: 'center',
        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
                title={this.props.dict['header.form.query.delete']}
                okText={this.props.dict['model.confirm']}
                cancelText={this.props.dict['header.cancel']}
                onConfirm={() => this.handleDelete(record.key)
              }>
                <span style={{color: '#1890ff', cursor: 'pointer'}}><Icon type="delete" /></span>
              </Popconfirm>
            </div>
          ) : null,
          inputType: n.type || 'text',
          dataIndex: n.key,
          handleSave: this.handleSave
        })
      }
    ]
    if (type === 'link') {
      columns.unshift({
        title: 'ParentID',
        dataIndex: 'ParentID',
        width: '27%',
        editable: true,
        datatype: dataItem && typeof(dataItem.ParentID) === 'number' ? 'number' : 'string'
      })
      if (n.width) {
        col.width = n.width
      }
      if (n.fixed) {
        delete col.onCell
      }
      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 fields
  }
  handleEmpty = () => {
    const { columns, module } = this.props
    const { dataSource } = this.state
    if (dataSource.filter(item => item.Value === '').length > 0) {
      message.warning('Value为空已存在!')
      return
    }
    let item = { key: Utils.getuuid() }
    columns.forEach(m => {
      item[m.key] = ''
    })
    item.Text = module === 'form' ? '空' : '全部'
    let _data = [item, ...dataSource]
    this.setState({
      columns: columns.map(col => {
        if (col.dataIndex !== 'operation') {
          col = {...col, ...this.getColumnSearchProps(col)}
        }
        return col
      }),
      dataSource: dataSource,
      type: type
      dataSource: _data,
    }, () => {
      this.props.onChange(_data)
    })
  }
  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
    })
    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)
    } else if (!is(fromJS(this.props.data), fromJS(nextProps.data))) {
      let _data = []
      nextProps.data.forEach(item => {
        let _item = {key: Utils.getuuid()}
        this.state.columns.forEach(col => {
          _item[col.dataIndex] = item[col.dataIndex] || ''
          if (col.dataIndex !== 'ParentID' && !_item[col.dataIndex]) {
            _item[col.dataIndex] = item.Text
          }
        })
        _data.push(_item)
      })
      this.setState({
        dataSource: _data,
        count: nextProps.data.length
    if (!is(fromJS(this.props.columns), fromJS(nextProps.columns))) {
      this.setState({}, () => {
        this.resetColumn()
      })
    }
  }
  render() {
    const { dataSource } = this.state
    const { module } = this.props
    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,
          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}
        />
        {module ? <span className="add-row add-row-empty" onClick={this.handleEmpty}>{module === 'form' ? '空' : '全部'}</span> : null}
        <PlusOutlined className="add-row" onClick={this.handleAdd} />
        <DndProvider>
          <Table
            components={components}
            rowClassName={(record) => record.Hide ? 'editable-row hide' : 'editable-row'}
            bordered
            dataSource={dataSource}
            columns={columns}
            pagination={false}
          />
        </DndProvider>
      </div>
    )
  }