From 9a11e62adeb8d435b52a361eb62d5b59e1deef2a Mon Sep 17 00:00:00 2001
From: king <18310653075@163.com>
Date: 星期四, 23 五月 2024 21:14:50 +0800
Subject: [PATCH] 2024-05-23

---
 src/templates/zshare/editTable/index.jsx |  816 +++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 697 insertions(+), 119 deletions(-)

diff --git a/src/templates/zshare/editTable/index.jsx b/src/templates/zshare/editTable/index.jsx
index bfeffd6..2c9ecc4 100644
--- a/src/templates/zshare/editTable/index.jsx
+++ b/src/templates/zshare/editTable/index.jsx
@@ -1,32 +1,143 @@
 import React, { Component } from 'react'
 import PropTypes from 'prop-types'
 import { is, fromJS } from 'immutable'
-import { Table, Input, InputNumber, Popconfirm, Form, Icon, Select, Radio, Cascader, notification } from 'antd'
+import { DndProvider, DragSource, DropTarget } from 'react-dnd'
+import { Table, Input, InputNumber, Popconfirm, Switch, Form, Select, Radio, Cascader, notification, message, Modal, Typography } from 'antd'
+import { CopyOutlined, EditOutlined, DeleteOutlined, SwapOutlined, PlusOutlined, ConsoleSqlOutlined } from '@ant-design/icons'
 
+import Utils from '@/utils/utils.js'
 import ColorSketch from '@/mob/colorsketch'
-import CusSwitch from './cusSwitch'
-import zhCN from '@/locales/zh-CN/model.js'
-import enUS from '@/locales/en-US/model.js'
+import asyncComponent from '@/utils/asyncComponent'
+import MKEmitter from '@/utils/events.js'
 import './index.scss'
 
-let eTDict = localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
+const MkEditIcon = asyncComponent(() => import('@/components/mkIcon'))
+const PasteBoard = asyncComponent(() => import('@/components/pasteboard'))
 const EditableContext = React.createContext()
+const { confirm } = Modal
+let dragingIndex = -1
+const { Paragraph } = Typography
+
+class CusSwitch extends Component {
+  static propTpyes = {
+    defaultValue: PropTypes.any,
+    value: PropTypes.any,
+    onChange: PropTypes.func
+  }
+  state = {
+    status: true
+  }
+
+  UNSAFE_componentWillMount () {
+    const { defaultValue, value } = this.props
+    let initVal = 'true'
+
+    if (this.props['data-__meta']) {
+      initVal = this.props['data-__meta'].initialValue
+    } else if (defaultValue) {
+      initVal = defaultValue
+    } else if (value) {
+      initVal = value
+    }
+
+    if (initVal === 'false') {
+      initVal = false
+    } else {
+      initVal = true
+    }
+    
+    this.setState({status: initVal})
+  }
+
+  changeStatus = (val) => {
+    this.setState({ status: val }, () => {
+      let _val = val ? 'true' : 'false'
+      this.props.onChange && this.props.onChange(_val)
+    })
+  }
+
+  render() {
+    const { status } = this.state
+    return (
+      <Switch checkedChildren="鏄�" unCheckedChildren="鍚�" checked={status} onChange={this.changeStatus} />
+    )
+  }
+}
+
+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
+    const { inputType, options, min, max, unlimit, allowClear, typeChange } = 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 />
+      return <ColorSketch allowClear={allowClear} />
+    } else if (inputType === 'icon') {
+      return <MkEditIcon allowClear/>
     } else if (inputType === 'switch') {
       return <CusSwitch />
     } else if (inputType === 'select') {
       return (
-        <Select>
+        <Select onChange={typeChange}>
           {options.map((item, i) => (<Select.Option key={i} value={item.field || item.value}> {item.label || item.text} </Select.Option>))}
         </Select>
       )
@@ -42,12 +153,14 @@
       )
     } else if (inputType === 'radio') {
       return (
-        <Radio.Group>
+        <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 if (inputType === 'textarea') {
+      return <Input.TextArea autoSize={true} placeholder=""/>
     } else {
-      return <Input onPressEnter={() => this.getValue(form)} />
+      return <Input onPressEnter={() => this.getValue(form)}/>
     }
   }
 
@@ -63,24 +176,32 @@
 
   renderCell = (form) => {
     const { getFieldDecorator } = form
-    const { editing, dataIndex, title, record, children, className, required, inputType } = this.props
+    const { editing, pass, dataIndex, title, record, children, className, required, inputType, rules } = this.props
 
+    if (!editing) {
+      return (
+        <td className={className}>
+          {children}
+        </td>
+      )
+    }
     return (
       <td className={className}>
-        {editing ? (
+        {pass ? (
           <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}!`,
-                }
+                  message: ['number', 'text', 'input'].includes(inputType) ? `璇疯緭鍏�${title}!` : `璇烽�夋嫨${title}!`,
+                },
+                ...rules
               ],
               initialValue: inputType === 'multiStr' ? (record[dataIndex] ? record[dataIndex].split(',') : []) : record[dataIndex],
             })(this.getInput(form))}
           </Form.Item>
         ) : (
-          children
+          null
         )}
       </td>
     )
@@ -94,6 +215,7 @@
 class EditTable extends Component {
   static propTpyes = {
     actions: PropTypes.any,         // 鎿嶄綔椤�
+    searchKey: PropTypes.any,       // 鎼滅储鏉′欢
     data: PropTypes.any,            // 鏁版嵁鍒楄〃
     columns: PropTypes.array,       // 鏄剧ず鍒�
     onChange: PropTypes.func        // 鏁版嵁鍙樺寲
@@ -102,79 +224,364 @@
   state = {
     data: [],
     editingKey: '',
-    columns: []
+    editLineId: '',
+    columns: [],
+    keyCol: null,
+    keyVal: ''
   }
 
   UNSAFE_componentWillMount () {
     const { data, actions } = this.props
     let columns = fromJS(this.props.columns).toJS()
+    let operation = null
+    let extra = null
+    let keyCol = null
+    
+    if (actions) {
+      actions.forEach(item => {
+        if (/^extra/.test(item)) {
+          extra = item.split(':')
+        }
+      })
+    }
 
-    columns.push({
-      title: eTDict['model.operation'],
-      dataIndex: 'operation',
-      width: '140px',
-      render: (text, record) => {
-        const { editingKey } = this.state
-        const editable = this.isEditing(record)
-        return editable ? (
-          <span style={{textAlign: 'center', display: 'block'}}>
-            <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>
+    if (actions && (actions.includes('edit') || actions.includes('copy') || actions.includes('del'))) {
+      let _operation = null
+      let render = null
+      columns = columns.filter(item => {
+        if (item.dataIndex === 'operation') {
+          _operation = item
+        }
+        if (item.keyCol) {
+          keyCol = item.dataIndex
+        }
+        if (item.dataIndex === 'sqlRender') {
+          render = item.render
+          return false
+        }
+        return item.dataIndex !== 'operation'
+      })
+
+      operation = {
+        title: (<div>
+          鎿嶄綔
+          <span className="copy-control">
+            {actions.includes('copy') ? <CopyOutlined title="澶嶅埗" onClick={() => this.copy()} /> : null}
+            {actions.includes('copy') ? <PasteBoard getPasteValue={this.pasteSubmit}/> : null}
+            {actions.includes('clear') ? <DeleteOutlined title="娓呯┖" onClick={this.clear} /> : null}
           </span>
-        ) : (
-          <div className={'edit-operation-btn' + (editingKey !== '' ? ' disabled' : '')}>
-            {!actions || actions.includes('edit') ? <span className="primary" onClick={() => {editingKey === '' && this.edit(record.uuid)}}><Icon type="edit" /></span> : null}
-            {!actions || actions.includes('up') ? <span className="primary" onClick={() => {editingKey === '' && this.handleUpDown(record.uuid, 'up')}}><Icon type="arrow-up" /></span> : null}
-            {!actions || actions.includes('down') ? <span className="danger" onClick={() => {editingKey === '' && this.handleUpDown(record.uuid, 'down')}}><Icon type="arrow-down" /></span> : null}
-            {(!actions || 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 || actions.includes('del')) && 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'}}>
+                    淇濆瓨
+                  </span>
+                )}
+              </EditableContext.Consumer>
+              <span style={{ color: '#1890ff', cursor: 'pointer'}} onClick={() => this.cancel(record.uuid)}>鍙栨秷</span>
+            </div>
+          ) : (
+            <div className={'edit-operation-btn' + (editingKey !== '' ? ' disabled' : '')} style={{minWidth: '110px', whiteSpace: 'nowrap'}}>
+              {actions.includes('edit') ? <span className="primary" title="缂栬緫" onClick={() => {editingKey === '' && this.edit(record)}}><EditOutlined /></span> : null}
+              {extra ? <span className="status" title={extra[2]} onClick={() => {editingKey === '' && this.handleStatus(record, extra[1])}}><SwapOutlined /></span> : null}
+              {actions.includes('status') ? <span className="status" title="鏄惁鍚敤" onClick={() => {editingKey === '' && this.handleStatus(record, 'status')}}><SwapOutlined /></span> : null}
+              {actions.includes('copy') ? <span className="copy" title="澶嶅埗" onClick={() => {editingKey === '' && this.copy(record)}}><CopyOutlined /></span> : null}
+              {actions.includes('del') && editingKey === '' ? <Popconfirm
+                overlayClassName="popover-confirm"
+                title="纭畾鍒犻櫎鍚�?"
+                onConfirm={() => this.handleDelete(record.uuid)
+                }>
+                <span className="danger"><DeleteOutlined /></span>
+              </Popconfirm> : null}
+              {actions.includes('del') && editingKey !== '' ? <span className="danger"><DeleteOutlined /></span> : null}
+              {actions.includes('sql') ? <span className="primary" title="SQL" onClick={() => {editingKey === '' && this.showSql(record, render)}}><ConsoleSqlOutlined /></span> : null}
+            </div>
+          )
+        }
       }
-    })
+
+      if (_operation) {
+        operation.render = _operation.render
+        operation.width = _operation.width
+      }
+      columns.push(operation)
+    }
 
     this.setState({
       data: data || [],
-      oricolumns: fromJS(this.props.columns).toJS(),
-      columns
+      operation,
+      columns,
+      keyCol
     })
   }
 
   UNSAFE_componentWillReceiveProps (nextProps) {
+
     if (!is(fromJS(this.state.data), fromJS(nextProps.data))) {
       this.setState({data: nextProps.data, editingKey: ''})
-    } else if (!is(fromJS(this.state.oricolumns), fromJS(nextProps.columns))) {
-      let cols = {}
-      nextProps.columns.forEach(col => {cols[col.dataIndex] = col})
-
-      this.setState({
-        oricolumns: fromJS(nextProps.columns).toJS(),
-        columns: this.state.columns.map(col => {
-          if (cols[col.dataIndex]) {
-            return cols[col.dataIndex]
-          }
-          return col
+    } 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)
+  }
+
+  showSql = (record, render) => {
+    let list = render(record)
+
+    if (list) {
+      Modal.info({
+        title: '',
+        width: 700,
+        className: 'sql-example',
+        icon: null,
+        content: list.map((n, index) => <div key={index} dangerouslySetInnerHTML={{ __html: n }}></div>)
       })
     }
+  }
+
+  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 {
+      let srcid = localStorage.getItem(window.location.href.split('#')[0] + 'srcId')
+      if (srcid) {
+        msg.$srcId = srcid
+      }
+      
+      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('澶嶅埗鎴愬姛銆�')
+    }
+  }
+  
+  pasteSubmit = (res, callback) => {
+    const { type } = this.props
+    const { columns } = this.state
+    let data = fromJS(this.state.data).toJS()
+
+    if (res.copyType === 'columns' && type === 'datasourcefield') {
+      res.type = 'array'
+      res.data = []
+      res.columns.forEach(col => {
+        if (!col.field) return
+        if (col.type === 'number') {
+          let datatype = 'Int'
+          if (col.decimal) {
+            datatype = `Decimal(18,${col.decimal})`
+          }
+
+          res.data.push({
+            $index: res.data.length + 1,
+            datatype: datatype,
+            field: col.field,
+            decimal: col.decimal,
+            label: col.label,
+            type: 'number',
+            uuid: Utils.getuuid()
+          })
+        } else {
+          let datatype = 'Nvarchar(50)'
+          let fieldlength = 50
+          if (col.fieldlength && [10, 20, 50, 100, 256, 512, 1024, 2048, 4000].includes(col.fieldlength)) {
+            fieldlength = col.fieldlength
+            datatype = `Nvarchar(${fieldlength})`
+          }
+
+          res.data.push({
+            $index: res.data.length + 1,
+            datatype: datatype,
+            field: col.field,
+            fieldlength: fieldlength,
+            label: col.label,
+            type: 'text',
+            uuid: Utils.getuuid()
+          })
+        }
+      })
+    } else 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
+
+        if (col.uniqueFunc) {
+          unique = col.uniqueFunc(data, res.data)
+        } else if (col.strict) {
+          let key = res.data[col.dataIndex].toLowerCase()
+          let _index = data.findIndex(item => key === item[col.dataIndex].toLowerCase())
+
+          if (_index > -1) {
+            notification.warning({
+              top: 92,
+              message: col.title + '涓嶅彲閲嶅锛�',
+              duration: 5
+            })
+            unique = false
+          }
+        } else {
+          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: '', editLineId: res.data.uuid || '' }, () => {
+        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
+
+          if (col.uniqueFunc) {
+            unique = col.uniqueFunc(data, cell)
+          } else if (col.strict) {
+            let _index = data.findIndex(item => cell[col.dataIndex].toLowerCase() === item[col.dataIndex].toLowerCase())
+  
+            if (_index > -1) {
+              unique = false
+            }
+          } else {
+            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: '' }, () => {
+        this.props.onChange(data)
+      })
+    }
+
+    callback()
+    message.success('绮樿创鎴愬姛銆�')
+  }
+
+  handleStatus = (record, type) => {
+    const { data } = this.state
+
+    record[type] = record[type] === 'false' ? 'true' : 'false'
+
+    let newData = data.map(item => {
+      if (record.uuid === item.uuid) return record
+
+      return item
+    })
+    
+    this.setState({ data: newData }, () => {
+      this.props.onChange(newData)
+    })
   }
 
   onSave = (record) => {
@@ -191,19 +598,56 @@
       return
     }
 
+    let forbid = false
+
+    columns.forEach(col => {
+      if (!col.forbids || forbid) return
+
+      let key = record[col.dataIndex].toLowerCase()
+      if (col.forbids.includes(key)) {
+        forbid = col.title + '涓嶅彲浣跨敤' + record[col.dataIndex]
+      }
+    })
+
+    if (forbid) {
+      notification.warning({
+        top: 92,
+        message: forbid,
+        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 (col.uniqueFunc) {
+        unique = col.uniqueFunc(newData, record)
+        return
+      } else if (col.strict) {
+        let key = record[col.dataIndex].toLowerCase()
+        let _index = newData.findIndex(item => record.uuid !== item.uuid && key === item[col.dataIndex].toLowerCase())
 
-      if (_index > -1) {
-        notification.warning({
-          top: 92,
-          message: col.title + '涓嶅彲閲嶅锛�',
-          duration: 5
-        })
-        unique = false
+        if (_index > -1) {
+          notification.warning({
+            top: 92,
+            message: col.title + '涓嶅彲閲嶅锛�',
+            duration: 5
+          })
+          unique = false
+        }
+      } else {
+        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
+        }
       }
     })
 
@@ -226,27 +670,6 @@
     })
   }
 
-  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) => {
@@ -255,31 +678,69 @@
       }
       const newData = [...this.state.data]
       const index = newData.findIndex(item => uuid === item.uuid)
+      if (index > -1) {
+        row = {...newData[index], ...row}
+      } else {
+        row.uuid = uuid
+      }
+
+      let forbid = false
+
+      columns.forEach(col => {
+        if (!col.forbids || forbid) return
+
+        let key = row[col.dataIndex].toLowerCase()
+        if (col.forbids.includes(key)) {
+          forbid = col.title + '涓嶅彲浣跨敤' + row[col.dataIndex]
+        }
+      })
+
+      if (forbid) {
+        notification.warning({
+          top: 92,
+          message: forbid,
+          duration: 5
+        })
+        return
+      }
 
       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 (col.uniqueFunc) {
+          unique = col.uniqueFunc(newData, row)
+          return
+        } else if (col.strict) {
+          let key = row[col.dataIndex].toLowerCase()
+          let _index = newData.findIndex(item => row.uuid !== item.uuid && key === item[col.dataIndex].toLowerCase())
+  
+          if (_index > -1) {
+            notification.warning({
+              top: 92,
+              message: col.title + '涓嶅彲閲嶅锛�',
+              duration: 5
+            })
+            unique = false
+          }
+        } else {
+          let _index = newData.findIndex(item => row.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,
-        })
+        newData.splice(index, 1, row)
         this.setState({ data: newData, editingKey: '' }, () => {
           this.props.onChange(newData)
         })
@@ -292,19 +753,86 @@
     })
   }
 
-  edit(uuid) {
-    this.setState({ editingKey: uuid })
+  edit(record) {
+    const { keyCol } = this.state
+
+    this.setState({ editingKey: record.uuid, keyVal: keyCol ? record[keyCol] : '' })
+  }
+
+  typeChange = (val) => {
+    this.setState({ keyVal: val })
+  }
+
+  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)
+    })
+  }
+
+  handleAdd = () => {
+    const { columns } = this.props
+    const { data } = this.state
+
+    let _index = data.length + 1
+    let item = {
+      uuid: Utils.getuuid()
+    }
+
+    columns.forEach(col => {
+      if (!col.dataIndex) return
+
+      item[col.dataIndex] = col.initval || ''
+
+      if (col.unique) {
+        while (data.filter(cell => cell[col.dataIndex] === item[col.dataIndex]).length > 0) {
+          _index++
+          item[col.dataIndex] = col.initval + _index
+        }
+      }
+    })
+
+    let _data = [...data, item]
+
+    this.setState({
+      data: _data
+    }, () => {
+      this.props.onChange(_data)
+    })
   }
 
   render() {
-    const components = {
+    const { actions, indexShow, searchKey } = this.props
+    const { editLineId, keyVal } = this.state
+
+    let components = {
       body: {
-        cell: EditableCell,
+        cell: EditableCell
       }
     }
+
+    let moveprops = {}
+    if (actions.includes('move') && !searchKey) {
+      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,
         onCell: record => ({
@@ -312,29 +840,79 @@
           inputType: col.inputType,
           dataIndex: col.dataIndex,
           options: col.options || [],
+          rules: col.rules || [],
           min: col.min || 0,
           max: col.max || 500,
           unlimit: col.unlimit,
           required: col.required !== false ? true : false,
+          allowClear: col.allowClear === true,
+          keyCol: col.keyCol === true,
           title: col.title,
           editing: this.isEditing(record),
+          pass: col.keyVals ? col.keyVals.includes(keyVal) : true,
           onSave: this.onSave,
+          typeChange: this.typeChange,
         }),
       }
     })
 
+    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
+    })
+
+    let reg = null
+    let regs = []
+
+    if (searchKey) {
+      reg = new RegExp(searchKey, 'i')
+      this.state.columns.forEach(col => {
+        if (col.searchable) {
+          regs.push(col.dataIndex)
+        }
+      })
+    }
+
     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}
-          />
+          {actions.includes('add') ? <PlusOutlined className="add-row" onClick={this.handleAdd} /> : null}
+          <DndProvider>
+            <Table
+              bordered
+              rowKey="uuid"
+              components={components}
+              dataSource={data}
+              columns={columns}
+              rowClassName={record => {
+                let className = 'editable-row'
+                if (editLineId && editLineId === record.uuid) {
+                  className += ' active'
+                }
+                if (searchKey) {
+                  if (regs.findIndex(f => reg.test(record[f])) === -1) {
+                    className += ' hidden'
+                  }
+                }
+                return className
+              }}
+              pagination={false}
+              onRow={(record, index) => ({
+                index,
+                ...moveprops
+              })}
+            />
+          </DndProvider>
         </div>
       </EditableContext.Provider>
     )

--
Gitblit v1.8.0