king
2019-12-22 9798a9fa338ee5d022793ae1ef91a49e2e2b4c62
2019-12-22
6个文件已修改
33个文件已添加
6919 ■■■■■ 已修改文件
src/components/sidemenu/editthdmenu/index.jsx 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/en-US/comtable.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/zh-CN/comtable.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/commontable/mainTable/index.jsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/colspanform/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/index.jsx 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/actionform/index.jsx 413 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/actionform/index.scss 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/colspanform/index.jsx 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/colspanform/index.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/columnform/index.jsx 219 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/columnform/index.scss 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/dragelement/card.jsx 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/dragelement/index.jsx 257 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/dragelement/index.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/dragelement/itemtypes.js 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/dragelement/source.jsx 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/editable/index.jsx 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/editable/index.scss 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/editcard/index.jsx 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/editcard/index.scss 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/gridbtnform/index.jsx 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/gridbtnform/index.scss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/index.jsx 2850 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/index.scss 478 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/menuform/index.jsx 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/menuform/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/searchform/index.jsx 341 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/searchform/index.scss 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/settingform/index.jsx 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/settingform/index.scss 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/source.jsx 362 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/tabdragelement/card.jsx 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/tabdragelement/index.jsx 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/tabdragelement/index.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/tabform/index.jsx 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/tabform/index.scss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/transferform/index.jsx 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/transferform/index.scss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/sidemenu/editthdmenu/index.jsx
@@ -18,6 +18,8 @@
const ComTableConfig = asyncLoadComponent(() => import('@/templates/comtableconfig'))
const ModalConfig = asyncLoadComponent(() => import('@/templates/modalconfig'))
const SubTable = asyncLoadComponent(() => import('@/templates/subtableconfig'))
const { confirm } = Modal
const { TabPane } = Tabs
const { Search } = Input
@@ -36,19 +38,20 @@
  }
  state = {
    dict: (!localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN') ? zhCN : enUS,
    show: true,
    thawmenulist: null, // 已冻结的二级菜单
    visible: null,
    title: '',
    type: '',
    tabview: '', // 选择模板窗口(template)、基础表格配置(CommonTable)
    tabview: '',           // 选择模板窗口(template)、基础表格配置(CommonTable)
    formlist: null,
    editMenu: null, // 编辑菜单
    editAction: null, // 编辑按钮
    thawMvisible: false, // 解除冻结模态框
    editMenu: null,        // 编辑菜单
    editAction: null,      // 编辑按钮
    editTab: null,         // 编辑标签
    thawMvisible: false,   // 解除冻结模态框
    confirmLoading: false, // 提交中。。。
    dict: (!localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN') ? zhCN : enUS,
    selectTemp: '', // 选择模板
    selectTemp: '',        // 选择模板
    usedTemplates: null,
    menuConfig: '',
    tempSearchKey: '',
@@ -383,18 +386,29 @@
    document.getElementById('root').style.overflowY = 'unset'
  }
  handleSubConfig = (item, originMenu, config) => {
    if (item.OpenType === 'pop') {
      item.pageParam = ''
      if (config && config.type === 'Modal') {
        item.pageParam = config
  handleSubConfig = (item, originMenu, config, type) => {
    if (type === 'button') {
      if (item.OpenType === 'pop') {
        item.pageParam = ''
        if (config && config.type === 'Modal') {
          item.pageParam = config
        }
        this.setState({
          editMenu: originMenu,
          editAction: item,
          tabview: 'Modal'
        })
      }
      this.setState({
        editMenu: originMenu,
        editAction: item,
        tabview: 'Modal'
      })
    } else if (type === 'tab') {
      console.log(item)
      // if (item.type === 'SubTable') {
      //   this.setState({
      //     editMenu: originMenu,
      //     editTab: item,
      //     tabview: 'SubTable'
      //   })
      // }
    }
  }
@@ -525,6 +539,13 @@
            handleConfig={this.handleConfig}
          />
        }
        {this.state.tabview === 'SubTable' &&
          <SubTable
            menu={this.state.editMenu}
            editTab={this.state.editTab}
            handleConfig={this.handleConfig}
          />
        }
        {/* 图片预览 */}
        <Preview cancel={this.cancelPrePicture} preview={this.state.preview} template={this.state.pretemplate} confirm={this.useTemplate}/>
        {/* 解冻菜单模态框 */}
src/locales/en-US/comtable.js
@@ -26,6 +26,7 @@
  'header.menu.search.add': 'Add searches',
  'header.menu.action': 'Button',
  'header.menu.action.configurable': 'Configurable Button',
  'header.menu.tab.configurable': 'Configurable Tab',
  'header.menu.column': 'Column',
  'header.menu.column.add': 'Add columns',
  'header.menu.page.configurable': 'Configuration Page',
@@ -151,6 +152,7 @@
  'header.form.order': '排列',
  'header.form.horizontal': '横向',
  'header.form.vertical': '纵向',
  'header.form.vertical2': '纵向(2列)',
  'header.form.topPicBottomText': '上图下文',
  'header.form.leftPicRightText': '左图右文',
  'header.form.selectItem.error': '下拉选项设置错误!',
src/locales/zh-CN/comtable.js
@@ -26,6 +26,7 @@
  'header.menu.search.add': '添加搜索条件',
  'header.menu.action': '按钮',
  'header.menu.action.configurable': '可配置按钮',
  'header.menu.tab.configurable': '可配置标签',
  'header.menu.column': '显示列',
  'header.menu.column.add': '添加显示列',
  'header.menu.page.configurable': '页面配置',
@@ -151,6 +152,7 @@
  'header.form.order': '排列',
  'header.form.horizontal': '横向',
  'header.form.vertical': '纵向',
  'header.form.vertical2': '纵向(2列)',
  'header.form.topPicBottomText': '上图下文',
  'header.form.leftPicRightText': '左图右文',
  'header.form.selectItem.error': '下拉选项设置错误!',
src/tabviews/commontable/mainTable/index.jsx
@@ -139,7 +139,8 @@
              content = +record[col.field]
            }
            if (content && col.format === 'thdSeparator') {
              content = `${content}`
              content = content.replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,')
            }
            content = (col.prefix || '') + content + (col.postfix || '')
          }
@@ -147,12 +148,23 @@
        })
      }
      if (contents && item.order === 'vertical2') {
        let _contents = []
        for(let i = 0; i < contents.length; i += 2) {
          _contents.push(contents.slice(i, i + 2).join(' '))
        }
        contents = _contents
      }
      return (
        <div>
          <div className="content" style={{ minWidth: (item.Width || 120) + 'px' }}>
            {contents && item.order === 'vertical' && contents.map((content, index) => {
              return (<p key={index}>{content}</p>)
            })}
            {contents && item.order === 'vertical2' && contents.map((content, index) => {
              return (<p key={index}>{content}</p>)
            })}
            {contents && item.order === 'horizontal' && contents.map((content, index) => {
              return (<span key={index}>{content}</span>)
            })}
src/templates/comtableconfig/colspanform/index.jsx
@@ -154,6 +154,7 @@
                >
                  <Select.Option value="vertical">{this.props.dict['header.form.vertical']}</Select.Option>
                  <Select.Option value="horizontal">{this.props.dict['header.form.horizontal']}</Select.Option>
                  <Select.Option value="vertical2">{this.props.dict['header.form.vertical2']}</Select.Option>
                  <Select.Option value="topPicBottomText">{this.props.dict['header.form.topPicBottomText']}</Select.Option>
                  <Select.Option value="leftPicRightText">{this.props.dict['header.form.leftPicRightText']}</Select.Option>
                </Select>
src/templates/comtableconfig/index.jsx
@@ -2356,7 +2356,7 @@
  /**
   * @description 设置可配置按钮
   */
  setSubConfig = (btn) => {
  setSubConfig = (btn, type) => {
    const { menu } = this.props
    const { config, originMenu } = this.state
@@ -2396,7 +2396,7 @@
            message: '菜单配置已修改,请保存!',
            duration: 10
          })
        } else {
        } else if (type === 'button') {
          this.setState({
            loading: true
          })
@@ -2417,7 +2417,7 @@
                  _LongParam = ''
                }
              }
              this.props.handleSubConfig(btn, originMenu, _LongParam)
              this.props.handleSubConfig(btn, originMenu, _LongParam, type)
            } else {
              this.setState({
                loading: false
@@ -2429,6 +2429,40 @@
              })
            }
          })
        } else if (type === 'tab') {
          this.props.handleSubConfig(btn, originMenu, '', type)
          // this.setState({
          //   loading: true
          // })
          // Api.getSystemConfig({
          //   func: 'sPC_Get_LongParam',
          //   MenuID: btn.uuid
          // }).then(res => {
          //   if (res.status) {
          //     this.setState({
          //       loading: false
          //     })
          //     let _LongParam = ''
          //     if (res.LongParam) {
          //       _LongParam = window.decodeURIComponent(window.atob(res.LongParam))
          //       try {
          //         _LongParam = JSON.parse(_LongParam)
          //       } catch (e) {
          //         _LongParam = ''
          //       }
          //     }
          //     this.props.handleSubConfig(btn, originMenu, _LongParam)
          //   } else {
          //     this.setState({
          //       loading: false
          //     })
          //     notification.warning({
          //       top: 92,
          //       message: res.message,
          //       duration: 10
          //     })
          //   }
          // })
        }
      }, () => {
        notification.warning({
@@ -2544,12 +2578,13 @@
                        icon={item.icon}
                        style={{marginBottom: '10px'}}
                        className={'mk-btn mk-' + item.class}
                        onClick={() => this.setSubConfig(item)}
                        onClick={() => this.setSubConfig(item, 'button')}
                      >{item.label}</Button>
                    </div>
                  )
                })}
              </Panel>
              {/* 添加显示列 */}
              <Panel header={this.state.dict['header.menu.column']} key="3">
                <div className="search-element">
                  {Source.columnItems.map((item, index) => {
@@ -2558,12 +2593,32 @@
                </div>
                <Button type="primary" block onClick={() => this.queryField('columns')}>{this.state.dict['header.menu.column.add']}</Button>
              </Panel>
              {/* 添加标签 */}
              <Panel header={this.state.dict['header.menu.tab']} key="4">
                <div className="search-element">
                  {Source.tabItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                {this.state.config.tabs.length > 0 ?
                  <p className="config-btn-title">
                    <Tooltip placement="topLeft" title="点击按钮,可完成或查看标签配置信息。">
                      <Icon type="question-circle" />
                    </Tooltip>
                    {this.state.dict['header.menu.tab.configurable']}
                  </p> : null
                }
                {this.state.config.tabs.map((item, index) => {
                  return (
                    <div key={index}>
                      <Button
                        icon={item.icon}
                        style={{marginBottom: '10px'}}
                        onClick={() => this.setSubConfig(item, 'tab')}
                      >{item.label}</Button>
                    </div>
                  )
                })}
              </Panel>
            </Collapse>
          </div>
src/templates/subtableconfig/actionform/index.jsx
New file
@@ -0,0 +1,413 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, Icon, Radio, notification, Tooltip } from 'antd'
import { btnIcons, btnClasses } from '@/utils/option.js'
import './index.scss'
const { TextArea } = Input
class MainSearch extends Component {
  static propTpyes = {
    dict: PropTypes.object, // 字典项
    formlist: PropTypes.any,
    card: PropTypes.any
  }
  state = {
    formlist: null,
    openType: null,
    interType: null,
    position: null,
    reqOptionSgl: [{
      MenuID: 'requiredSgl',
      text: this.props.dict['header.form.requiredSgl']
    }],
    reqOptions: [{
      MenuID: 'notRequired',
      text: this.props.dict['header.form.notRequired']
    }, {
      MenuID: 'requiredSgl',
      text: this.props.dict['header.form.requiredSgl']
    }],
    reqOptionsMutil: [{
      MenuID: 'notRequired',
      text: this.props.dict['header.form.notRequired']
    }, {
      MenuID: 'requiredSgl',
      text: this.props.dict['header.form.requiredSgl']
    }, {
      MenuID: 'required',
      text: this.props.dict['header.form.required']
    }, {
      MenuID: 'requiredOnce',
      text: this.props.dict['header.form.requiredOnce']
    }],
    insertUpdateOptions: [{
      MenuID: '',
      text: this.props.dict['header.form.empty']
    }, {
      MenuID: 'insert',
      text: this.props.dict['header.form.action.insert']
    }, {
      MenuID: 'update',
      text: this.props.dict['header.form.action.update']
    }],
    deleteOptions: [{
      MenuID: '',
      text: this.props.dict['header.form.empty']
    }, {
      MenuID: 'LogicDelete',
      text: this.props.dict['header.form.action.LogicDelete']
    }, {
      MenuID: 'delete',
      text: this.props.dict['header.form.action.delete']
    }]
  }
  UNSAFE_componentWillMount () {
    let _opentype = this.props.formlist.filter(form => form.key === 'OpenType')[0].initVal
    let _intertype = this.props.formlist.filter(form => form.key === 'intertype')[0].initVal
    let _position = this.props.formlist.filter(form => form.key === 'position')[0].initVal
    let _options = null
    if (_opentype === 'innerpage') { // 新页面(内部),可选模板
      _options = ['label', 'Ot', 'OpenType', 'pageTemplate', 'icon', 'class', 'position']
    } else if (_opentype === 'outerpage') { // 新页面(外部),需要页面地址
      _options = ['label', 'Ot', 'OpenType', 'url', 'icon', 'class', 'position']
    } else if (_opentype === 'blank' || _opentype === 'tab' || _opentype === 'popview') {
      _options = ['label', 'Ot', 'OpenType', 'icon', 'class', 'position']
    } else if (_opentype === 'excelIn' || _opentype === 'excelOut') {
      if (_intertype === 'outer') {
        _options = ['label', 'OpenType', 'intertype', 'innerFunc', 'interface', 'outerFunc', 'callbackFunc', 'icon', 'class', 'execSuccess', 'execError', 'method']
      } else {
        _options = ['label', 'OpenType', 'intertype', 'innerFunc', 'icon', 'class', 'execSuccess', 'execError']
      }
    } else {
      if (_intertype === 'outer') {
        _options = ['label', 'position', 'OpenType', 'intertype', 'innerFunc', 'interface', 'outerFunc', 'callbackFunc', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'method']
      } else {
        _options = ['label', 'position', 'OpenType', 'intertype', 'innerFunc', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'sql', 'sqlType']
      }
    }
    this.setState({
      openType: _opentype,
      interType: _intertype,
      position: _position,
      formlist: this.props.formlist.map(item => {
        if (item.key === 'class') {
          item.options = btnClasses
        } else if (item.key === 'icon') {
          item.options = btnIcons
        } else if (item.key === 'Ot') {
          if (_opentype === 'innerpage' || _position === 'grid') {
            item.options = this.state.reqOptionSgl
          } else if (['outerpage', 'blank', 'tab', 'popview'].includes(_opentype)) {
            item.options = this.state.reqOptions
          } else {
            item.options = this.state.reqOptionsMutil
          }
        } else if (item.key === 'sqlType') {
          if (['prompt', 'exec'].includes(_opentype)) {
            item.options = this.state.deleteOptions
          } else {
            item.options = this.state.insertUpdateOptions
          }
        }
        item.hidden = !_options.includes(item.key)
        return item
      })
    })
  }
  // 打开方式或显示位置变化
  openTypeChange = (key, value) => {
    if (key === 'OpenType') {
      let _options = null
      if (value === 'innerpage') {
        _options = ['label', 'Ot', 'OpenType', 'pageTemplate', 'icon', 'class', 'position']
      } else if (value === 'outerpage') {
        _options = ['label', 'Ot', 'OpenType', 'url', 'icon', 'class', 'position']
      } else if (value === 'blank' || value === 'tab' || value === 'popview') {
        _options = ['label', 'Ot', 'OpenType', 'icon', 'class', 'position']
      } else if (value === 'excelIn' || value === 'excelOut') {
        if (this.state.interType === 'outer') {
          _options = ['label', 'OpenType', 'intertype', 'innerFunc', 'interface', 'outerFunc', 'callbackFunc', 'icon', 'class', 'execSuccess', 'execError', 'method']
        } else {
          _options = ['label', 'OpenType', 'intertype', 'innerFunc', 'icon', 'class', 'execSuccess', 'execError']
        }
      } else {
        if (this.state.interType === 'inner') {
          _options = ['label', 'position', 'OpenType', 'intertype', 'innerFunc', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'sql', 'sqlType']
        } else {
          _options = ['label', 'position', 'OpenType', 'intertype', 'innerFunc', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'interface', 'outerFunc', 'callbackFunc', 'method']
        }
      }
      this.setState({
        openType: value,
        formlist: this.state.formlist.map(item => {
          if (_options) {
            item.hidden = !_options.includes(item.key)
            if (item.key === 'intertype') {
              item.initVal = this.state.interType
            }
          }
          if (item.key === 'Ot') {
            if (value === 'innerpage' || this.state.position === 'grid') {
              item.options = this.state.reqOptionSgl
              item.initVal = 'requiredSgl'
            } else if (['outerpage', 'blank', 'tab', 'popview'].includes(value)) {
              item.options = this.state.reqOptions
              item.initVal = 'requiredSgl'
            } else {
              item.options = this.state.reqOptionsMutil
            }
            item.hidden = true
          } else if (item.key === 'sqlType') {
            if (['prompt', 'exec'].includes(value)) {
              item.options = this.state.deleteOptions
            } else {
              item.options = this.state.insertUpdateOptions
            }
            item.initVal = ''
            item.hidden = true
          }
          return item
        })
      }, () => {
        if (['excelIn', 'excelOut'].includes(value)) return
        this.setState({
          formlist: this.state.formlist.map(item => {
            if (item.key === 'Ot') {
              item.hidden = false
            } else if (item.key === 'sqlType' && ['prompt', 'exec', 'pop'].includes(value)) {
              item.hidden = false
            }
            return item
          })
        })
      })
    } else if (key === 'position') {
      this.setState({
        position: value,
        formlist: this.state.formlist.map(item => {
          if (item.key === 'Ot') {
            if (this.state.openType === 'innerpage' || value === 'grid') {
              item.options = this.state.reqOptionSgl
              item.initVal = 'requiredSgl'
              item.hidden = true
            } else if (['outerpage', 'blank', 'tab', 'pop', 'popview'].includes(this.state.openType)) {
              item.options = this.state.reqOptions
              item.initVal = 'requiredSgl'
              item.hidden = true
            } else {
              item.options = this.state.reqOptionsMutil
            }
          }
          return item
        })
      }, () => {
        this.setState({
          formlist: this.state.formlist.map(item => {
            if (item.key === 'Ot') {
              item.hidden = false
            }
            return item
          })
        })
      })
    }
  }
  onChange = (e, key) => {
    let value = e.target.value
    if (key === 'intertype') {
      let _options = null
      if (value === 'inner') {
        _options = ['label', 'position', 'OpenType', 'intertype', 'innerFunc', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'sql', 'sqlType']
      } else {
        _options = ['label', 'position', 'OpenType', 'intertype', 'innerFunc', 'Ot', 'icon', 'class', 'execSuccess', 'execError', 'interface', 'outerFunc', 'callbackFunc', 'method']
      }
      this.setState({
        interType: value,
        formlist: this.state.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          return item
        })
      })
    }
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.state.formlist.forEach((item, index) => {
      if (item.hidden) return
      if (item.type === 'text') { // 文本搜索
        let _rules = []
        if (item.key === 'innerFunc') {
          let str = '^(' + item.fields.join('|') + ')'
          let _patten = new RegExp(str + '[0-9a-zA-Z_]*$', 'g')
          _rules = [{
            pattern: _patten,
            message: '名称只允许包含数字、字母和下划线,且以指定字符开始。'
          }, {
            max: 50,
            message: '内部函数名称不超过50个字符。'
          }]
        }
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" overlayClassName={item.tooltipClass} title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: item.readonly ? false : !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  },
                  ..._rules
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" overlayClassName={item.tooltipClass} title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children[2].toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.openTypeChange(item.key, value)}}
                  getPopupContainer={() => document.getElementById('winter')}
                >
                  {item.options.map(option =>
                    <Select.Option id={option.MenuID} title={option.text} key={option.MenuID} value={option.MenuID}>
                      {item.key === 'icon' && option.MenuID && <Icon type={option.MenuID} />} {option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onChange(e, item.key)}}>
                  {
                    item.options.map(option => {
                      return (
                        <Radio key={option.MenuID} value={option.MenuID}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'textarea') {
        fields.push(
          <Col span={24} key={index}>
            <Form.Item label={item.label} className="textarea">
              {getFieldDecorator(item.key, {
                initialValue: item.initVal
              })(<TextArea rows={4} />)}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          values.id = this.props.card.id
          values.uuid = this.props.card.uuid
          if (values.OpenType === 'excelIn' || values.OpenType === 'excelOut') {
            values.position = 'toolbar'
            values.Ot = 'notRequired'
          }
          if (values.innerFunc === '' && values.sql === '') {
            notification.warning({
              top: 92,
              message: this.props.dict['header.form.actionhelp.datasource'],
              duration: 10
            })
          } else if (values.innerFunc === '' && values.sql !== '' && values.sqlType === '') {
            notification.warning({
              top: 92,
              message: this.props.dict['header.form.actionhelp.sqlType'],
              duration: 10
            })
          } else {
            resolve({
              type: 'action',
              values
            })
          }
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 7 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 17 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-action-form" id="winter">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/templates/subtableconfig/actionform/index.scss
New file
@@ -0,0 +1,20 @@
.ant-advanced-search-form.commontable-action-form {
  min-height: 190px;
  .superconfig {
    color: #1890ff;
    cursor: pointer;
  }
  .textarea {
    .ant-col-sm-7 {
      width: 14%;
    }
    .ant-col-sm-17 {
      width: 86%;
    }
  }
  .anticon-question-circle {
    color: #c49f47;
    position: relative;
    left: -3px;
  }
}
src/templates/subtableconfig/colspanform/index.jsx
New file
@@ -0,0 +1,173 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, InputNumber, Select, Radio } from 'antd'
import TransferForm from '../transferform'
import './index.scss'
class MainSearch extends Component {
  static propTpyes = {
    dict: PropTypes.object, // 字典项
    columns: PropTypes.array,
    card: PropTypes.any
  }
  state = {
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          let targetKeys = this.refs['column-transfer'].state.targetKeys
          delete values.type // 删除type,此处值为'合并列'文字
          let subfield = []  // 用于查看合并列字段
          this.props.columns.forEach(col => {
            if (col.field && targetKeys.includes(col.uuid)) {
              subfield.push(col.field)
            }
          })
          subfield = subfield.join(', ')
          let _card = {...this.props.card, ...values, sublist: targetKeys, subfield: subfield}
          resolve({
            type: 'columns',
            values: _card
          })
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 6 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 18 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-column-form" id="columncolspan">
        <Row gutter={24}>
          <Col span={12}>
            <Form.Item label={this.props.dict['header.form.name']}>
              {getFieldDecorator('label', {
                initialValue: this.props.card.label,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + this.props.dict['header.form.name'] + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={this.props.dict['header.form.type']}>
              {getFieldDecorator('type', {
                initialValue: this.props.dict['header.form.colspan'],
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + this.props.dict['header.form.type'] + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={true}/>)}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={this.props.dict['header.form.align']}>
              {getFieldDecorator('Align', {
                initialValue: this.props.card.Align,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + this.props.dict['header.form.align'] + '!'
                  }
                ]
              })(
                <Select
                  getPopupContainer={() => document.getElementById('columncolspan')}
                >
                  <Select.Option value="left">{this.props.dict['header.form.alignLeft']}</Select.Option>
                  <Select.Option value="right">{this.props.dict['header.form.alignRight']}</Select.Option>
                  <Select.Option value="center">{this.props.dict['header.form.alignCenter']}</Select.Option>
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={this.props.dict['header.form.columnWidth']}>
              {getFieldDecorator('Width', {
                initialValue: this.props.card.Width,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + this.props.dict['header.form.columnWidth'] + '!'
                  }
                ]
              })(<InputNumber min={1} max={1000} precision={0} />)}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={this.props.dict['header.form.Hide']}>
              {getFieldDecorator('Hide', {
                initialValue: this.props.card.Hide,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + this.props.dict['header.form.Hide'] + '!'
                  }
                ]
              })(
                <Radio.Group>
                  <Radio value="true">{this.props.dict['header.form.true']}</Radio>
                  <Radio value="false">{this.props.dict['header.form.false']}</Radio>
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={this.props.dict['header.form.order']}>
              {getFieldDecorator('order', {
                initialValue: this.props.card.order,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + this.props.dict['header.form.order'] + '!'
                  }
                ]
              })(
                <Select
                  getPopupContainer={() => document.getElementById('columncolspan')}
                >
                  <Select.Option value="vertical">{this.props.dict['header.form.vertical']}</Select.Option>
                  <Select.Option value="horizontal">{this.props.dict['header.form.horizontal']}</Select.Option>
                  <Select.Option value="vertical2">{this.props.dict['header.form.vertical2']}</Select.Option>
                  <Select.Option value="topPicBottomText">{this.props.dict['header.form.topPicBottomText']}</Select.Option>
                  <Select.Option value="leftPicRightText">{this.props.dict['header.form.leftPicRightText']}</Select.Option>
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={24}>
            <TransferForm dict={this.props.dict} columns={this.props.columns} ref="column-transfer" selected={this.props.card.sublist}/>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/templates/subtableconfig/colspanform/index.scss
New file
@@ -0,0 +1,9 @@
.ant-advanced-search-form.commontable-column-form {
  min-height: 190px;
  .ant-form-item {
    margin-bottom: 15px;
    .ant-input-number {
      width: 100%;
    }
  }
}
src/templates/subtableconfig/columnform/index.jsx
New file
@@ -0,0 +1,219 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, InputNumber, Radio, Tooltip, Icon } from 'antd'
import './index.scss'
class MainSearch extends Component {
  static propTpyes = {
    dict: PropTypes.object, // 字典项
    formlist: PropTypes.any,
    card: PropTypes.any
  }
  state = {
    formlist: null
  }
  UNSAFE_componentWillMount () {
    let _type = this.props.formlist.filter(form => form.key === 'type')[0].initVal
    let _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width']
    if (_type === 'text') {
      _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'matchVal', 'color']
    } else if (_type === 'number') {
      _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'match', 'matchVal', 'color']
    } else if (_type === 'textarea') {
      _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'matchVal', 'color']
    }
    this.setState({
      formlist: this.props.formlist.map(item => {
        item.hidden = !_options.includes(item.key)
        if (item.key === 'matchVal' && (_type === 'text' || _type === 'textarea')) {
          item.type = 'text'
        } else if (item.key === 'matchVal' && _type === 'number') {
          item.type = 'number'
        }
        return item
      })
    })
  }
  typeChange = (key, value) => {
    if (key === 'type') {
      let _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width']
      if (value === 'text') {
        _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'matchVal', 'color']
      } else if (value === 'number') {
        _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'match', 'matchVal', 'color']
      } else if (value === 'textarea') {
        _options = ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'matchVal', 'color']
      }
      this.setState({
        formlist: this.props.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          if (item.key === 'matchVal' && (value === 'text' || value === 'textarea')) {
            item.type = 'text'
          } else if (item.key === 'matchVal' && value === 'number') {
            item.type = 'number'
            item.initVal = ''
            item.hidden = true
          }
          return item
        })
      }, () => {
        this.setState({
          formlist: this.props.formlist.map(item => {
            if (item.key === 'matchVal' && value === 'number') {
              item.hidden = false
            }
            return item
          })
        })
      })
    }
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.state.formlist.forEach((item, index) => {
      if (item.hidden) return
      if (item.type === 'text') { // 文本搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" overlayClassName={item.tooltipClass} title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'number') { // 文本搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<InputNumber min={item.min} max={item.max} precision={item.decimal} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.typeChange(item.key, value)}}
                  getPopupContainer={() => document.getElementById('columnwinter')}
                >
                  {item.options.map(option =>
                    <Select.Option id={option.MenuID} title={option.text} key={option.MenuID} value={option.MenuID}>
                      {option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Radio.Group>
                  {
                    item.options.map(option => {
                      return (
                        <Radio key={option.MenuID} value={option.MenuID}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          values.id = this.props.card.id
          values.uuid = this.props.card.uuid
          resolve({
            type: 'columns',
            values
          })
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 6 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 18 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-column-form" id="columnwinter">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/templates/subtableconfig/columnform/index.scss
New file
@@ -0,0 +1,14 @@
.ant-advanced-search-form.commontable-column-form {
  min-height: 190px;
  .ant-form-item {
    margin-bottom: 15px;
    .ant-input-number {
      width: 100%;
    }
  }
  .anticon-question-circle {
    color: #c49f47;
    position: relative;
    left: -3px;
  }
}
src/templates/subtableconfig/dragelement/card.jsx
New file
@@ -0,0 +1,143 @@
import React from 'react'
import { useDrag, useDrop } from 'react-dnd'
import { Icon, Button, Select, DatePicker, Input } from 'antd'
import moment from 'moment'
import ItemTypes from './itemtypes'
import './index.scss'
const { MonthPicker, WeekPicker, RangePicker } = DatePicker
const Card = ({ id, type, card, moveCard, findCard, editCard, delCard, copyCard, hasDrop, showfield }) => {
  const originalIndex = findCard(id).index
  const [{ isDragging }, drag] = useDrag({
    item: { type: ItemTypes[type], id, originalIndex },
    collect: monitor => ({
      isDragging: monitor.isDragging(),
    }),
  })
  const [, drop] = useDrop({
    accept: ItemTypes[type],
    canDrop: () => true,
    drop: (item) => {
      if (!item.hasOwnProperty('originalIndex')) {
        hasDrop(card)
      }
    },
    hover({ id: draggedId }) {
      if (!draggedId) return
      if (draggedId !== id) {
        const { index: overIndex } = findCard(id)
        moveCard(draggedId, overIndex)
      }
    },
  })
  const opacity = isDragging ? 0 : 1
  const edit = () => {
    editCard(id)
  }
  const del = () => {
    delCard(id)
  }
  const copy = () => {
    copyCard(id)
  }
  let _defaultValue = '' // 下拉搜索、时间范围类型,初始值需要预处理
  if (type === 'search' && card.type === 'select') {
    if (card.initval) {
      let _option = card.options.filter(option => option.Value === card.initval)[0]
      if (_option) {
        _defaultValue = _option.Text || ''
      } else {
        _defaultValue = ''
      }
    } else if (card.setAll === 'true') {
      _defaultValue = '全部'
    }
  } else if (type === 'search' && card.type === 'daterange') {
    _defaultValue = [null, null]
    if (card.initval) {
      try {
        let _initval = JSON.parse(card.initval)
        _defaultValue = [moment().subtract(_initval[0], 'days'), moment().subtract(_initval[1], 'days')]
      } catch {
        _defaultValue = [null, null]
      }
    }
  }
  return (
    <div className="page-card" style={type === 'columns' ? { flex: card.Width, opacity: opacity} : { opacity: opacity}}>
      <div ref={node => drag(drop(node))}>
        {type === 'search' ?
          <div className="ant-row ant-form-item">
            <div className="ant-col ant-form-item-label">
              <label title={card.label}>{card.label}</label>
            </div>
            <div className="ant-col ant-form-item-control-wrapper">
              {card.type === 'text' ?
                <Input style={{marginTop: '4px'}} defaultValue={card.initval} /> : null
              }
              {(card.type === 'select' || card.type === 'link') ?
                <Select defaultValue={_defaultValue}></Select> : null
              }
              {card.type === 'date' ?
                <DatePicker defaultValue={card.initval ? moment().subtract(card.initval, 'days') : null} /> : null
              }
              {card.type === 'dateweek' ?
                <WeekPicker defaultValue={card.initval ? moment().subtract(card.initval * 7, 'days') : null} /> : null
              }
              {card.type === 'datemonth' ?
                <MonthPicker defaultValue={card.initval ? moment().subtract(card.initval, 'month') : null} /> : null
              }
              {card.type === 'daterange' ?
                <RangePicker
                  className="data-range"
                  placeholder={['开始日期', '结束日期']}
                  renderExtraFooter={() => 'extra footer'}
                  defaultValue={_defaultValue}
                /> : null
              }
              <div className="input-mask"></div>
            </div>
          </div> : null
        }
        {type === 'action' ?
          <Button
            className={'mk-btn mk-' + card.class}
            icon={card.icon}
            key={card.uuid}
          >
            {card.label}{card.position === 'grid' && <Icon type="table" />}
          </Button> : null
        }
        {type === 'columns' ?
          <span className="ant-table-header-column">
            <div className="ant-table-column-sorters" title={card.label} style={{textAlign: card.Align}}>
              <span className="ant-table-column-title">{card.label}</span>
              {card.IsSort === 'true' ?
                <span className="ant-table-column-sorter">
                  <Icon type="caret-up" />
                  <Icon type="caret-down" />
                </span> : null
              }
            </div>
            {showfield ?
              <div className="ant-table-column-fields">
                <span className="ant-table-column-title">{card.type === 'colspan' ? card.subfield : card.field}</span>
              </div> : null
            }
          </span> : null
        }
      </div>
      <Icon className="edit" type="edit" onClick={edit} />
      <Icon className="edit close" type="close" onClick={del} />
      {type === 'action' ? <Icon className="edit copy" type="copy" onClick={copy} /> : null}
    </div>
  )
}
export default Card
src/templates/subtableconfig/dragelement/index.jsx
New file
@@ -0,0 +1,257 @@
import React, { useState } from 'react'
import { useDrop } from 'react-dnd'
import update from 'immutability-helper'
import { Col, Icon } from 'antd'
import Utils from '@/utils/utils.js'
import Card from './card'
import ItemTypes from './itemtypes'
import './index.scss'
const Container = ({list, setting, gridBtn, type, placeholder, handleList, handleMenu, deleteMenu, copyElement, handleGridBtn, showfield }) => {
  let target = null
  const [cards, setCards] = useState(list)
  const moveCard = (id, atIndex) => {
    const { card, index } = findCard(id)
    const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
    setCards(_cards)
    handleList(type, _cards)
  }
  const findCard = id => {
    const card = cards.filter(c => `${c.uuid}` === id)[0]
    return {
      card,
      index: cards.indexOf(card),
    }
  }
  const editCard = id => {
    const { card } = findCard(id)
    handleMenu(card)
  }
  const delCard = id => {
    const { card } = findCard(id)
    deleteMenu({card: card, type: type})
  }
  const copyCard = id => {
    const { card } = findCard(id)
    let copycard = JSON.parse(JSON.stringify(card))
    copycard.uuid = Utils.getuuid()
    copycard.origin = false
    copycard.label = copycard.label + '(copy)'
    copycard.originCard = card
    copyElement(copycard)
  }
  const hasDrop = (item) => {
    target = item
  }
  const [, drop] = useDrop({
    accept: ItemTypes[type],
    drop(item) {
      if (item.hasOwnProperty('originalIndex')) {
        return
      }
      let newcard = {}
      if (item.type === 'search') {
        let _match = 'like'
        if (item.subType === 'select' || item.subType === 'link') {
          _match = '='
        } else if (item.subType === 'date' || item.subType === 'datemonth') {
          _match = '>='
        } else if (item.subType === 'dateweek' || item.subType === 'daterange') {
          _match = 'between'
        }
        newcard.uuid = Utils.getuuid()
        newcard.label = 'label'
        newcard.field = ''
        newcard.initval = ''
        newcard.type = item.subType
        newcard.resourceType = '0'
        newcard.options = []
        newcard.dataSource = ''
        newcard.setAll = 'false'
        newcard.linkField = ''
        newcard.valueField = ''
        newcard.valueText = ''
        newcard.orderBy = ''
        newcard.orderType = 'asc'
        newcard.match = _match
        newcard.display = 'dropdown'
      } else if (item.type === 'action') {
        newcard.uuid = Utils.getuuid()
        newcard.label = 'button'
        newcard.innerFunc = ''
        newcard.outerFunc = ''
        newcard.sql = ''
        newcard.sqlType = ''
        newcard.Ot = 'requiredSgl'
        newcard.OpenType = item.subType
        newcard.icon = ''
        newcard.class = 'default'
        newcard.intertype = 'inner'
        newcard.interface = ''
        newcard.method = 'POST'
        newcard.position = 'toolbar'
        newcard.execSuccess = 'grid'
        newcard.execError = 'never'
        newcard.callbackFunc = ''
        newcard.pageTemplate = ''
        newcard.url = ''
        if (item.subType === 'excelIn' || item.subType === 'excelOut') {
          // 导入和导出excel,按钮名称直接为导入、导出
          newcard.label = item.label
        }
      } else if (item.type === 'columns') {
        newcard.uuid = Utils.getuuid()
        newcard.Align = 'left'
        newcard.label = 'label'
        newcard.field = ''
        newcard.Hide = 'false'
        newcard.IsSort = 'true'
        newcard.type = item.subType
        newcard.Width = 120
        if (item.subType === 'colspan') {
          newcard.sublist = []
          newcard.subfield = []
          newcard.IsSort = 'false'
          newcard.order = 'vertical'
        }
      }
      let targetId = cards.length > 0 ? cards[cards.length - 1].uuid : 0
      if (target) {
        targetId = target.uuid
      }
      const { index: overIndex } = findCard(`${targetId}`)
      let targetIndex = overIndex
      if (!target) {
        targetIndex++
      }
      if (targetIndex < 0) {
        targetIndex = 0
      }
      const _cards = update(cards, { $splice: [[targetIndex, 0, newcard]] })
      setCards(_cards)
      handleList(type, _cards, newcard)
      target = null
    }
  })
  let columns = []
  let _colCards = []
  // 过滤合并列
  if (type === 'columns') {
    let _hideCol = []
    cards.forEach(col => {
      if (col.type === 'colspan' && col.sublist) {
        _hideCol = _hideCol.concat(col.sublist)
      }
    })
    cards.forEach(col => {
      if (!_hideCol.includes(col.uuid)) {
        _colCards.push(col)
      }
    })
  }
  // 显示列分行
  if (type === 'columns' && _colCards.length > 10) {
    let number = Math.ceil(_colCards.length / Math.ceil(_colCards.length / 10))
    for (let i = 0, len = _colCards.length; i < len; i += number) {
      columns.push(_colCards.slice(i, i + number))
    }
  } else if (type === 'columns') {
    columns.push(_colCards)
  }
  return (
    <div ref={drop} className="ant-row">
      {type === 'action' && cards.map(card => (
        <Card
          key={card.uuid}
          id={`${card.uuid}`}
          type={type}
          card={card}
          moveCard={moveCard}
          editCard={editCard}
          delCard={delCard}
          copyCard={copyCard}
          findCard={findCard}
          hasDrop={hasDrop}
        />
      ))}
      {type === 'search' && cards.map(card => (
        <Col key={card.uuid} span={6}>
          <Card
            id={`${card.uuid}`}
            type={type}
            card={card}
            moveCard={moveCard}
            editCard={editCard}
            delCard={delCard}
            findCard={findCard}
            hasDrop={hasDrop}
          />
        </Col>
      ))}
      {type === 'columns' && _colCards.length > 0 &&
        columns.map((column, i) => (
          <div key={i} className="column-box">
            {/* 多选 */}
            {i === 0 && setting.tableType === 'checkbox' ?
              <div className="page-card" style={{flex: 60}}>
                <span className="ant-checkbox-inner"></span>
              </div> : null
            }
            {/* 单选 */}
            {i === 0 && setting.tableType === 'radio' ?
              <div className="page-card" style={{flex: 60}}></div> : null
            }
            {column.map(card => (
              <Card
                key={card.uuid}
                id={`${card.uuid}`}
                type={type}
                card={card}
                showfield={showfield}
                moveCard={moveCard}
                editCard={editCard}
                delCard={delCard}
                findCard={findCard}
                hasDrop={hasDrop}
              />
            ))}
            {i === (columns.length - 1) && gridBtn && gridBtn.display ?
              <div className="page-card" style={{flex: gridBtn.Width}}>
                <div style={{cursor: 'default'}}>
                  <span className="ant-table-header-column">
                    <div className="ant-table-column-sorters" title={gridBtn.label} style={{textAlign: gridBtn.Align}}>
                      <span className="ant-table-column-title">{gridBtn.label}</span>
                    </div>
                  </span>
                </div>
                <Icon className="edit" type="edit" onClick={handleGridBtn}/>
              </div> : null
            }
          </div>
        ))
      }
      {cards.length === 0 &&
        <div className="common-drawarea-placeholder">
          {placeholder}
        </div>
      }
    </div>
  )
}
export default Container
src/templates/subtableconfig/dragelement/index.scss
New file
@@ -0,0 +1,15 @@
.common-source-item {
  display: block;
  box-shadow: 0px 0px 2px #bcbcbc;
  padding: 0.4rem 0.7rem;
  background-color: white;
  margin: 0px 0px 10px;
  cursor: move;
  border-radius: 4px;
}
.common-drawarea-placeholder {
  width: 100%;
  line-height: 65px;
  text-align: center;
  color: #bcbcbc;
}
src/templates/subtableconfig/dragelement/itemtypes.js
New file
@@ -0,0 +1,8 @@
export default {
  CARD: 'card',
  form: 'form',
  search: 'search',
  action: 'action',
  columns: 'columns',
  tab: 'tab'
}
src/templates/subtableconfig/dragelement/source.jsx
New file
@@ -0,0 +1,13 @@
import React from 'react'
import { useDrag } from 'react-dnd'
import './index.scss'
const SourceElement = ({content}) => {
  const [, drag] = useDrag({ item: content })
  return (
    <div ref={drag} className="common-source-item">
      {content.label}
    </div>
  )
}
export default SourceElement
src/templates/subtableconfig/editable/index.jsx
New file
@@ -0,0 +1,258 @@
import React, {Component} from 'react'
import { Table, Input, Button, Popconfirm, Form, Icon } from 'antd'
import Utils from '@/utils/utils.js'
import './index.scss'
const EditableContext = React.createContext()
const EditableRow = ({ form, index, ...props }) => (
  <EditableContext.Provider value={form}>
    <tr {...props} />
  </EditableContext.Provider>
)
const EditableFormRow = Form.create()(EditableRow)
class EditableCell extends Component {
  state = {
    editing: false
  }
  toggleEdit = () => {
    const editing = !this.state.editing
    this.setState({ editing }, () => {
      if (editing) {
        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()
      // handleSave({ ...record, ...values })
    })
  }
  renderCell = form => {
    this.form = form
    const { children, dataIndex, record } = this.props
    const { editing } = this.state
    return editing ? (
      <Form.Item style={{ margin: 0 }}>
        {form.getFieldDecorator(dataIndex, {
          rules: [
            {
              required: true,
              message: 'NOT NULL.',
            },
          ],
          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>
    )
  }
  render() {
    const {
      editable,
      dataIndex,
      title,
      record,
      index,
      handleSave,
      children,
      ...restProps
    } = this.props
    return (
      <td {...restProps}>
        {editable ? (
          <EditableContext.Consumer style={{padding: 0}}>{this.renderCell}</EditableContext.Consumer>
        ) : (
          children
        )}
      </td>
    )
  }
}
class EditTable extends Component {
  constructor(props) {
    super(props)
    let columns = [
      {
        title: 'Value',
        dataIndex: 'Value',
        width: props.type === 'link' ? '27%' : '40%',
        editable: true
      },
      {
        title: 'Text',
        dataIndex: 'Text',
        width: props.type === 'link' ? '27%' : '40%',
        editable: true
      },
      {
        title: '操作',
        align: 'center',
        dataIndex: 'operation',
        render: (text, record) =>
          this.state.dataSource.length >= 1 ? (
            <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
              <span style={{color: '#1890ff', cursor: 'pointer'}}><Icon type="delete" /></span>
            </Popconfirm>
          ) : null,
      }
    ]
    if (props.type === 'link') {
      columns.unshift({
        title: 'ParentID',
        dataIndex: 'ParentID',
        width: '27%',
        editable: true
      })
    }
    this.state = {
      columns: columns,
      dataSource: props.data,
      count: props.data.length,
      type: props.type
    }
  }
  handleDelete = key => {
    const dataSource = [...this.state.dataSource]
    this.setState({ dataSource: dataSource.filter(item => item.key !== key) })
  }
  handleAdd = () => {
    const { type, count, dataSource } = this.state
    const newData = {
      key: Utils.getuuid(),
      Value: `${count}`,
      Text: `${count}`
    }
    if (type === 'link') {
      newData.ParentID = `${count}`
    }
    this.setState({
      dataSource: [...dataSource, newData],
      count: count + 1
    })
  }
  handleSave = row => {
    const newData = [...this.state.dataSource]
    const index = newData.findIndex(item => row.key === item.key)
    const item = newData[index]
    newData.splice(index, 1, {
      ...item,
      ...row
    })
    this.setState({ dataSource: newData })
  }
  resetColumn = (type) => {
    let columns = [
      {
        title: 'Value',
        dataIndex: 'Value',
        width: type === 'link' ? '27%' : '40%',
        editable: true
      },
      {
        title: 'Text',
        dataIndex: 'Text',
        width: type === 'link' ? '27%' : '40%',
        editable: true
      },
      {
        title: '操作',
        align: 'center',
        dataIndex: 'operation',
        render: (text, record) =>
          this.state.dataSource.length >= 1 ? (
            <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
              <span style={{color: '#1890ff', cursor: 'pointer'}}><Icon type="delete" /></span>
            </Popconfirm>
          ) : null,
      }
    ]
    if (type === 'link') {
      columns.unshift({
        title: 'ParentID',
        dataIndex: 'ParentID',
        width: '27%',
        editable: true
      })
    }
    this.setState({
      columns: columns,
      type: type
    })
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    if (this.props.type !== nextProps.type) {
      this.resetColumn(nextProps.type)
    }
  }
  render() {
    const { dataSource } = 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">
        <Button onClick={this.handleAdd} type="primary" className="add-row">
          添加
        </Button>
        <Table
          components={components}
          rowClassName={() => 'editable-row'}
          bordered
          dataSource={dataSource}
          columns={columns}
          pagination={false}
        />
      </div>
    )
  }
}
export default EditTable
src/templates/subtableconfig/editable/index.scss
New file
@@ -0,0 +1,36 @@
.common-modal-edit-table {
  .add-row {
    position: absolute;
    z-index: 1;
    right: 12px;
    top: -40px;
  }
  .ant-table-thead > tr > th {
    padding: 10px 16px;
  }
  .ant-table-tbody > tr > td {
    padding: 0px 16px;
  }
  .editable-cell-value-wrap {
    cursor: pointer;
    height: 40px;
    width: 100px;
    display: table-cell;
    vertical-align: middle;
    word-wrap: break-word;
    word-break: break-word;
    .ant-input {
      height: 30px;
      padding: 0 11px;
    }
  }
  .ant-form-item-control-wrapper {
    width: 100%;
  }
  .ant-table-placeholder {
    padding: 5px 16px;
    .ant-empty-normal {
      margin: 0;
    }
  }
}
src/templates/subtableconfig/editcard/index.jsx
New file
@@ -0,0 +1,152 @@
import React, {Component} from 'react'
import { Row, Col, Icon, Radio, Input, Button } from 'antd'
import './index.scss'
const { Search } = Input
class EditCardCell extends Component {
  constructor(props) {
    super(props)
    let _type = props.card.type
    if (props.type === 'columns') {
      if (_type === 'date' || _type === 'datetime') {
        _type = 'text'
      }
    } else if (props.type === 'search') {
      if (_type === 'number') {
        _type = 'text'
      } else if (_type === 'datetime') {
        _type = 'daterange'
      }
    }
    this.state = {
      card: {...props.card, type: _type},
      type: props.type
    }
  }
  changeSelect = () => {
    const { card } = this.state
    this.setState({
      card: {...card, selected: !card.selected}
    }, () => {
      this.props.changeCard(this.state.card)
    })
  }
  changeType = (e) => {
    const { card } = this.state
    this.setState({
      card: {...card, type: e.target.value}
    }, () => {
      this.props.changeCard(this.state.card)
    })
  }
  render() {
    const { card, type } = this.state
    return (
      <div className={'ant-card ant-card-bordered ' + (card.selected ? 'selected' : '')} >
        <div className="base" onClick={this.changeSelect}>
          <Icon type="check" />
          <p title={card.field}>{this.props.dict['header.form.field']}: <span>{card.field}</span></p>
          <p title={card.label}>{this.props.dict['header.form.name']}: <span>{card.label}</span></p>
        </div>
        {type === 'search' ?
          <Radio.Group onChange={this.changeType} value={card.type} disabled={!card.selected}>
            <Radio value="text">text</Radio>
            <Radio value="select">select</Radio>
            <Radio value="daterange">dateRange</Radio>
          </Radio.Group> : null
        }
        {type === 'columns' ?
          <Radio.Group onChange={this.changeType} value={card.type} disabled={!card.selected}>
            <Radio value="text">text</Radio>
            <Radio value="number">number</Radio>
            <Radio value="picture">picture</Radio>
          </Radio.Group> : null
        }
      </div>
    )
  }
}
class EditCard extends Component {
  constructor(props) {
    super(props)
    this.state = {
      dataSource: props.data,
      selectCards: props.data.filter(item => item.selected),
      type: props.type,
      searchKey: '',
      loading: false
    }
  }
  changeCard = (item) => {
    let cards = JSON.parse(JSON.stringify(this.state.selectCards))
    let isAdd = true
    cards = cards.map(card => {
      if (card.field === item.field) {
        isAdd = false
        return item
      } else {
        return card
      }
    })
    if (isAdd) {
      cards.push(item)
    }
    this.setState({
      selectCards: cards
    })
  }
  reset = () => {
    this.setState({
      searchKey: '',
      loading: true
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  render() {
    const { dataSource, type, loading } = this.state
    return (
      <div className="common-modal-edit-card">
        <Row className="search-row">
          <Col span={8}>
            {!loading ? <Search placeholder={this.props.dict['header.form.field.placeholder']} onSearch={value => {this.setState({searchKey: value})}} enterButton /> : null}
          </Col>
          <Col span={8}>
            <Button onClick={this.reset}>
              {this.props.dict['header.reset']}
            </Button>
          </Col>
        </Row>
        <Row>
          {dataSource.map((item, index) => {
            if (item.field.toLowerCase().indexOf(this.state.searchKey.toLowerCase()) >= 0) {
              return (
                <Col key={index} span={8}>
                  <EditCardCell ref={'cellCard' + index} type={type} card={item} dict={this.props.dict} changeCard={this.changeCard} />
                </Col>
              )
            } else {
              return ''
            }
          })}
        </Row>
      </div>
    )
  }
}
export default EditCard
src/templates/subtableconfig/editcard/index.scss
New file
@@ -0,0 +1,49 @@
.common-modal-edit-card {
  margin-left: -10px;
  margin-right: -10px;
  .ant-col {
    padding: 10px;
    .ant-card {
      padding: 0px 10px 10px;
      p {
        margin-bottom: 5px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      label {
        margin-right: 15px;
        span.ant-radio + * {
          padding-right: 0px;
          padding-left: 4px;
        }
      }
      .anticon {
        position: absolute;
        top: 10px;
        right: 10px;
        opacity: 0.4;
      }
      .base {
        padding-top: 10px;
        cursor: pointer;
      }
    }
    .ant-card.selected {
      border-color: #1890ff;
      box-shadow: 0px 0px 4px #1890ff;
      .anticon {
        opacity: 1;
        color: #1890ff;
      }
      p {
        color: #1890ff;
      }
    }
  }
  .search-row {
    .ant-col {
      padding-top: 0px;
    }
  }
}
src/templates/subtableconfig/gridbtnform/index.jsx
New file
@@ -0,0 +1,204 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, InputNumber, Radio } from 'antd'
import './index.scss'
class MainSearch extends Component {
  static propTpyes = {
    dict: PropTypes.object, // 字典项
    card: PropTypes.any
  }
  state = {
    formlist: [
      {
        type: 'text',
        key: 'label',
        label: this.props.dict['header.form.name'],
        initVal: this.props.card.label,
        required: true
      },
      {
        type: 'select',
        key: 'Align',
        label: this.props.dict['header.form.align'],
        initVal: this.props.card.Align,
        required: true,
        options: [{
          MenuID: 'left',
          text: this.props.dict['header.form.alignLeft']
        }, {
          MenuID: 'right',
          text: this.props.dict['header.form.alignRight']
        }, {
          MenuID: 'center',
          text: this.props.dict['header.form.alignCenter']
        }]
      },
      {
        type: 'number',
        key: 'Width',
        decimal: 0,
        label: this.props.dict['header.form.columnWidth'],
        initVal: this.props.card.Width,
        required: true
      },
      {
        type: 'select',
        key: 'style',
        label: this.props.dict['header.form.style'],
        initVal: this.props.card.style,
        required: true,
        options: [{
          MenuID: 'button',
          text: this.props.dict['header.form.button']
        }, {
          MenuID: 'text',
          text: this.props.dict['header.form.text']
        }]
      },
      {
        type: 'select',
        key: 'show',
        label: this.props.dict['header.form.order'],
        initVal: this.props.card.show,
        required: true,
        options: [{
          MenuID: 'horizontal',
          text: this.props.dict['header.form.horizontal']
        }, {
          MenuID: 'vertical',
          text: this.props.dict['header.form.vertical']
        }]
      }
    ]
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.state.formlist.forEach((item, index) => {
      if (item.type === 'text') { // 文本搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'number') { // 文本搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<InputNumber min={1} max={1000} precision={item.decimal} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  getPopupContainer={() => document.getElementById('columnwinter')}
                >
                  {item.options.map(option =>
                    <Select.Option id={option.MenuID} title={option.text} key={option.MenuID} value={option.MenuID}>
                      {option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Radio.Group>
                  {
                    item.options.map(option => {
                      return (
                        <Radio key={option.MenuID} value={option.MenuID}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve({...this.props.card, ...values})
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 6 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 18 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-column-form" id="columnwinter">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/templates/subtableconfig/gridbtnform/index.scss
New file
@@ -0,0 +1,9 @@
.ant-advanced-search-form.commontable-column-form {
  min-height: 190px;
  .ant-form-item {
    margin-bottom: 15px;
    .ant-input-number {
      width: 100%;
    }
  }
}
src/templates/subtableconfig/index.jsx
New file
@@ -0,0 +1,2850 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import { Button, Card, Modal, Collapse, notification, Spin, Select, List, Icon, Empty, Switch, Tooltip } from 'antd'
import moment from 'moment'
import DragElement from './dragelement'
import TabDragElement from './tabdragelement'
import SourceElement from './dragelement/source'
import Api from '@/api'
import TabForm from './tabform'
import SearchForm from './searchform'
import ActionForm from './actionform'
import ColumnForm from './columnform'
import ColspanForm from './colspanform'
import SettingForm from './settingform'
import GridBtnForm from './gridbtnform'
import EditCard from './editcard'
import MenuForm from './menuform'
import zhCN from '@/locales/zh-CN/comtable.js'
import enUS from '@/locales/en-US/comtable.js'
import Utils from '@/utils/utils.js'
import Source from './source'
import './index.scss'
const { Panel } = Collapse
const { Option } = Select
const { confirm } = Modal
const CommonDict = (!localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN') ? zhCN : enUS
class ComTableConfig extends Component {
  static propTpyes = {
    type: PropTypes.string,
    menu: PropTypes.any,
    reloadmenu: PropTypes.func,
    handleConfig: PropTypes.func,
    handleSubConfig: PropTypes.func,
    supMenuList: PropTypes.array
  }
  state = {
    dict: CommonDict,        // 字典
    config: null,            // 页面配置
    visible: false,          // 搜索条件、按钮、显示列,模态框显示控制
    modalTitle: '',          // 模态框的标题
    tableVisible: false,     // 数据表字段模态框
    addType: '',             // 添加类型-搜索条件或显示列
    tableColumns: [],        // 表格显示列
    fields: null,            // 搜索条件及显示列,可选字段
    menuformlist: null,      // 基本信息表单字段
    formlist: null,          // 搜索条件、按钮、显示列表单字段
    formtemp: '',            // 表单类型,显示列、按钮、搜索条件
    card: null,              // 编辑元素
    searchloading: false,    // 搜索条件加载中
    actionloading: false,    // 按钮加载中
    columnsloading: false,   // 显示列加载中
    tabloading: false,       // 标签页加载中
    menuloading: false,      // 菜单保存中
    menucloseloading: false, // 菜单关闭时,选择保存
    loading: false,          // 加载中,页面spin
    settingVisible: false,   // 全局配置模态框
    closeVisible: false,     // 关闭模态框
    tables: [],              // 可用表名
    selectedTables: [],      // 已选表名
    originMenu: null,        // 原始菜单
    originActions: null,     // 原始按钮信息,使用已有用户模板
    delActions: [],          // 删除按钮列表
    funcLoading: false,      // 存储过程创建中
    showColumnName: false    // 显示列字段名控制
  }
  /**
   * @description 数据预处理
   * 1、设置页面配置信息,新建或无配置信息时(切换模板后无配置信息),使用模板默认配置
   * 2、设置操作类型、原始菜单信息(每次保存后重置)、已使用表及基本信息表单
   */
  UNSAFE_componentWillMount () {
    const { menu } = this.props
    let _LongParam = menu.LongParam
    let _config = ''
    if (!_LongParam) {
      _config = JSON.parse(JSON.stringify((Source.baseConfig)))
    } else {
      let _setting = Source.baseConfig.setting
      if (_LongParam.setting) {
        _setting = {..._setting, ..._LongParam.setting}
      }
      _LongParam.setting = _setting
      _config = _LongParam
    }
    let _oriActions = []
    if (_config.type === 'user') {
      _config.action = _config.action.map(item => {
        let uuid = Utils.getuuid()
        if (item.OpenType === 'pop') { // 含有子配置项的按钮。。。
          _oriActions.push({
            prebtn: JSON.parse(JSON.stringify(item)),
            curuuid: uuid,
            Template: 'Modal'
          })
        }
        item.uuid = uuid
        return item
      })
    }
    _config.tabs = _config.tabs || []
    this.setState({
      originActions: _oriActions,
      config: _config,
      originMenu: JSON.parse(JSON.stringify(menu)),
      selectedTables: _config.tables || [],
      menuformlist: [
        {
          type: 'select',
          key: 'parentId',
          label: this.state.dict['header.menu.supMenu'],
          initVal: menu.ParentID,
          required: true,
          readonly: false,
          options: this.props.supMenuList
        },
        {
          type: 'text',
          key: 'menuName',
          label: this.state.dict['header.menu.menuName'],
          initVal: menu.MenuName,
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'menuNo',
          label: this.state.dict['header.menu.menuNo'],
          initVal: menu.MenuNo,
          required: true,
          readonly: false
        },
        {
          type: 'select',
          key: 'opentype',
          label: this.state.dict['header.menu.openType'],
          initVal: menu.PageParam.OpenType,
          required: true,
          options: [{
            MenuID: 'newtab',
            text: this.state.dict['header.form.tab']
          }, {
            MenuID: 'newpage',
            text: this.state.dict['header.form.newpage']
          }, {
            MenuID: 'currenttab',
            text: this.state.dict['header.form.currenttab']
          }]
        }
      ]
    })
  }
  /**
   * @description 加载完成后
   * 1、获取系统可使用表
   * 2、根据配置信息中已使用表获取相关字段信息
   */
  componentDidMount () {
    let param = {
      func: 'sPC_Get_SelectedList',
      LText: 'select TbName ,Remark from sDataDictionary where IsKey!=\'\' and Deleted =0',
      obj_name: 'data',
      arr_field: 'TbName,Remark'
    }
    param.LText = Utils.formatOptions(param.LText)
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
    param.secretkey = Utils.encrypt(param.LText, param.timestamp)
    Api.getSystemConfig(param).then(res => {
      if (res.status) {
        this.setState({
          tables: res.data
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    })
    let deffers = this.state.selectedTables.map(item => {
      return new Promise(resolve => {
        Api.getSystemConfig({func: 'sPC_Get_FieldName', TBName: item.TbName}).then(res => {
          res.TBName = item.TbName
          resolve(res)
        })
      })
    })
    Promise.all(deffers).then(response => {
      let _columns = []
      response.forEach(res => {
        if (res.status) {
          let tabmsg = {
            tableName: res.TBName,
            columns: res.FDName.map(item => {
              let _type = item.FieldType.toLowerCase()
              let _decimal = 0
              if (/^nvarchar/.test(_type)) {
                _type = 'text'
              } else if (/^int/.test(_type)) {
                _type = 'number'
              } else if (/^decimal/.test(_type)) {
                _decimal = _type.split(',')[1]
                _decimal = parseInt(_decimal)
                if (_decimal > 4) {
                  _decimal = 4
                }
                _type = 'number'
              } else if (/^decimal/.test(_type)) {
                _decimal = _type.split(',')[1]
                _decimal = parseInt(_decimal)
                if (_decimal > 4) {
                  _decimal = 4
                }
                _type = 'number'
              } else if (/^datetime/.test(_type)) {
                _type = 'datetime'
              } else if (/^date/.test(_type)) {
                _type = 'date'
              } else {
                _type = 'text'
              }
              return {
                field: item.FieldName,
                label: item.FieldDec,
                type: _type,
                datatype: _type,
                decimal: _decimal
              }
            })
          }
          _columns.push(tabmsg)
        } else {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
        }
      })
      this.setState({
        tableColumns: _columns
      })
    })
  }
  handleList = (type, list, card) => {
    const { config } = this.state
    if (list.length > config[type].length) {
      list = list.filter(item => !item.origin)
      this.setState({
        [type + 'loading']: true,
        config: {...config, [type]: list }
      }, () => {
        // 刷新对应的配置信息
        this.setState({
          [type + 'loading']: false
        })
        if (type === 'search') {
          this.handleSearch(card)
        } else if (type === 'action') {
          this.handleAction(card)
        } else if (type === 'columns') {
          this.handleColumn(card)
        } else if (type === 'tabs') {
          this.handleTab(card)
        }
      })
    } else {
      this.setState({config: {...config, [type]: list}})
    }
  }
  handleSearch = (card) => {
    this.setState({
      visible: true,
      formtemp: 'search',
      modalTitle: '编辑-搜索条件',
      card: card,
      formlist: [
        {
          type: 'text',
          key: 'label',
          label: this.state.dict['header.form.name'],
          initVal: card.label,
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'field',
          label: this.state.dict['header.form.field'],
          initVal: card.field,
          required: true,
          readonly: false
        },
        {
          type: 'select',
          key: 'type',
          label: this.state.dict['header.form.type'],
          initVal: card.type,
          required: true,
          options: [{
            value: 'text',
            text: this.state.dict['header.form.text']
          }, {
            value: 'select',
            text: this.state.dict['header.form.select']
          }, {
            value: 'link',
            text: this.state.dict['header.form.link']
          }, {
            value: 'date',
            text: this.state.dict['header.form.dateday']
          }, {
            value: 'dateweek',
            text: this.state.dict['header.form.dateweek']
          }, {
            value: 'datemonth',
            text: this.state.dict['header.form.datemonth']
          }, {
            value: 'daterange',
            text: this.state.dict['header.form.daterange']
          }]
        },
        {
          type: 'text',
          key: 'initval',
          label: this.state.dict['header.form.initval'],
          initVal: card.initval,
          required: false
        },
        {
          type: 'radio',
          key: 'resourceType',
          label: this.state.dict['header.form.resourceType'],
          initVal: card.resourceType || '0',
          required: true,
          options: [{
            value: '0',
            text: this.state.dict['header.form.custom']
          }, {
            value: '1',
            text: this.state.dict['header.form.datasource']
          }]
        },
        {
          type: 'radio',
          key: 'setAll',
          label: this.state.dict['header.form.setAll'],
          initVal: card.setAll || 'false',
          options: [{
            value: 'true',
            text: this.state.dict['header.form.true']
          }, {
            value: 'false',
            text: this.state.dict['header.form.false']
          }]
        },
        {
          type: 'textarea',
          key: 'dataSource',
          label: this.state.dict['header.form.datasource'],
          initVal: card.dataSource || '',
          required: true,
          readonly: false
        },
        {
          type: 'options',
          key: 'options',
          label: '',
          initVal: card.options || [],
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'linkField',
          label: this.state.dict['header.form.linkField'],
          initVal: card.linkField || '',
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'valueField',
          label: this.state.dict['header.form.valueField'],
          initVal: card.valueField || '',
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'valueText',
          label: this.state.dict['header.form.valueText'],
          initVal: card.valueText || '',
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'orderBy',
          label: this.state.dict['header.form.orderBy'],
          initVal: card.orderBy || '',
          required: false,
          readonly: false
        },
        {
          type: 'select',
          key: 'orderType',
          label: this.state.dict['header.form.orderType'],
          initVal: card.orderType || 'asc',
          options: [{
            value: 'asc',
            text: this.state.dict['header.form.asc']
          }, {
            value: 'desc',
            text: this.state.dict['header.form.desc']
          }]
        },
        {
          type: 'select',
          key: 'match',
          label: this.state.dict['header.form.match'],
          initVal: card.match || 'like',
          required: true,
          options: [{
            value: 'like',
            text: 'like'
          }, {
            value: 'equal',
            text: 'equal'
          }, {
            value: 'greater',
            text: '>'
          }, {
            value: 'less',
            text: '<'
          }, {
            value: 'greaterequal',
            text: '>='
          }]
        },
        {
          type: 'select',
          key: 'display',
          label: this.state.dict['header.form.display'],
          initVal: card.display || 'dropdown',
          required: true,
          options: [{
            value: 'dropdown',
            text: this.state.dict['header.form.dropdown']
          }, {
            value: 'button',
            text: this.state.dict['header.form.button']
          }]
        }
      ]
    })
  }
  handleAction = (card, type) => {
    let ableField = this.props.permFuncField.join(', ')
    this.setState({
      visible: true,
      formtemp: 'action',
      modalTitle: type === 'copy' ? '复制-按钮' : '编辑-按钮',
      card: card,
      formlist: [
        {
          type: 'text',
          key: 'label',
          label: this.state.dict['header.form.name'],
          initVal: card.label,
          required: true,
          readonly: false
        },
        {
          type: 'select',
          key: 'OpenType',
          label: this.state.dict['header.form.openType'],
          initVal: card.OpenType,
          required: true,
          options: [{
            MenuID: 'pop',
            text: this.state.dict['header.form.popform']
          }, {
            MenuID: 'popview',
            text: this.state.dict['header.form.popview']
          }, {
            MenuID: 'prompt',
            text: this.state.dict['header.form.prompt']
          }, {
            MenuID: 'exec',
            text: this.state.dict['header.form.exec']
          }, {
            MenuID: 'tab',
            text: this.state.dict['header.form.tab']
          }, {
            MenuID: 'excelIn',
            text: this.state.dict['header.form.excelIn']
          }, {
            MenuID: 'excelOut',
            text: this.state.dict['header.form.excelOut']
          }, {
            MenuID: 'blank',
            text: this.state.dict['header.form.blank']
          }, {
            MenuID: 'innerpage',
            text: this.state.dict['header.form.newpage.inner']
          }, {
            MenuID: 'outerpage',
            text: this.state.dict['header.form.newpage.outer']
          }]
        },
        {
          type: 'select',
          key: 'pageTemplate',
          label: this.state.dict['header.form.pageTemplate'],
          initVal: card.pageTemplate,
          required: true,
          options: []
        },
        {
          type: 'text',
          key: 'url',
          label: this.state.dict['header.form.newpage.url'],
          initVal: card.url || '',
          required: true
        },
        {
          type: 'radio',
          key: 'intertype',
          label: this.state.dict['header.form.intertype'],
          initVal: card.intertype,
          required: true,
          options: [{
            MenuID: 'inner',
            text: this.state.dict['header.form.interface.inner']
          }, {
            MenuID: 'outer',
            text: this.state.dict['header.form.interface.outer']
          }]
        },
        {
          type: 'text',
          key: 'innerFunc',
          label: this.state.dict['header.form.innerFunc'],
          initVal: card.innerFunc,
          tooltip: <div>
            <p>内部接口: 可自定义数据处理函数,函数名称需以{ableField}等字符开始;未设置时会调用系统函数,使用系统函数需完善数据源及操作类型;</p>
            <p>外部接口: 可自定义数据处理函数,提交数据经过内部函数处理后,传入外部接口,未设置时,数据会直接传入外部接口。</p>
          </div>,
          fields: this.props.permFuncField,
          tooltipClass: 'middle',
          required: false,
          readonly: false
        },
        {
          type: 'text',
          key: 'interface',
          label: this.state.dict['header.form.interface'],
          initVal: card.interface,
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'outerFunc',
          label: this.state.dict['header.form.outerFunc'],
          initVal: card.outerFunc,
          required: false,
          readonly: false
        },
        {
          type: 'text',
          key: 'callbackFunc',
          label: this.state.dict['header.form.callbackFunc'],
          initVal: card.callbackFunc,
          required: false,
          readonly: false
        },
        {
          type: 'select',
          key: 'position',
          label: this.state.dict['header.form.position'],
          initVal: card.position || 'toolbar',
          required: true,
          options: [{
            MenuID: 'toolbar',
            text: this.state.dict['header.form.toolbar']
          }, {
            MenuID: 'grid',
            text: this.state.dict['header.form.grid']
          }]
        },
        {
          type: 'select',
          key: 'Ot',
          label: this.state.dict['header.form.isRequired'],
          initVal: card.Ot || 'requiredSgl',
          required: true,
          options: []
        },
        {
          type: 'select',
          key: 'execSuccess',
          label: this.state.dict['header.form.execSuccess'],
          initVal: card.execSuccess || 'never',
          required: true,
          options: [{
            MenuID: 'never',
            text: this.state.dict['header.form.refresh.never']
          }, {
            MenuID: 'grid',
            text: this.state.dict['header.form.refresh.grid']
          }, {
            MenuID: 'view',
            text: this.state.dict['header.form.refresh.view']
          }]
        },
        {
          type: 'select',
          key: 'execError',
          label: this.state.dict['header.form.execError'],
          initVal: card.execError || 'never',
          required: true,
          options: [{
            MenuID: 'never',
            text: this.state.dict['header.form.refresh.never']
          }, {
            MenuID: 'grid',
            text: this.state.dict['header.form.refresh.grid']
          }, {
            MenuID: 'view',
            text: this.state.dict['header.form.refresh.view']
          }]
        },
        {
          type: 'select',
          key: 'icon',
          label: this.state.dict['header.form.icon'],
          initVal: card.icon,
          required: false,
          options: []
        },
        {
          type: 'select',
          key: 'class',
          label: this.state.dict['header.form.class'],
          initVal: card.class,
          required: false,
          options: []
        },
        {
          type: 'text',
          key: 'sql',
          label: this.state.dict['header.form.datasource'],
          initVal: card.sql || this.state.config.setting.tableName || '',
          tooltip: this.state.dict['header.form.actionhelp.datasource'],
          required: false
        },
        {
          type: 'select',
          key: 'sqlType',
          label: this.state.dict['header.form.action.type'],
          initVal: card.sqlType || '',
          tooltip: this.state.dict['header.form.actionhelp.sqlType'],
          required: false,
          options: []
        }
      ]
    })
  }
  handleColumn = (card) => {
    if (card.type !== 'colspan') {
      this.setState({
        visible: true,
        formtemp: 'columns',
        modalTitle: '编辑-显示列',
        card: card,
        formlist: [
          {
            type: 'text',
            key: 'label',
            label: this.state.dict['header.form.name'],
            initVal: card.label,
            required: true
          },
          {
            type: 'text',
            key: 'field',
            label: this.state.dict['header.form.field'],
            initVal: card.field,
            required: true,
            readonly: false
          },
          {
            type: 'select',
            key: 'type',
            label: this.state.dict['header.form.type'],
            initVal: card.type,
            required: true,
            options: [{
              MenuID: 'text',
              text: this.state.dict['header.form.text']
            }, {
              MenuID: 'picture',
              text: this.state.dict['header.form.picture']
            }, {
              MenuID: 'number',
              text: this.state.dict['header.form.number']
            }, {
              MenuID: 'textarea',
              text: this.state.dict['header.form.textarea']
            }]
          },
          {
            type: 'select',
            key: 'Align',
            label: this.state.dict['header.form.align'],
            initVal: card.Align,
            required: true,
            options: [{
              MenuID: 'left',
              text: this.state.dict['header.form.alignLeft']
            }, {
              MenuID: 'right',
              text: this.state.dict['header.form.alignRight']
            }, {
              MenuID: 'center',
              text: this.state.dict['header.form.alignCenter']
            }]
          },
          {
            type: 'radio',
            key: 'Hide',
            label: this.state.dict['header.form.Hide'],
            initVal: card.Hide,
            required: true,
            options: [{
              MenuID: 'true',
              text: this.state.dict['header.form.true']
            }, {
              MenuID: 'false',
              text: this.state.dict['header.form.false']
            }]
          },
          {
            type: 'radio',
            key: 'IsSort',
            label: this.state.dict['header.form.IsSort'],
            initVal: card.IsSort,
            required: true,
            options: [{
              MenuID: 'true',
              text: this.state.dict['header.form.true']
            }, {
              MenuID: 'false',
              text: this.state.dict['header.form.false']
            }]
          },
          {
            type: 'number',
            key: 'Width',
            min: 1,
            max: 1000,
            decimal: 0,
            label: this.state.dict['header.form.columnWidth'],
            initVal: card.Width,
            required: true
          },
          {
            type: 'number',
            key: 'decimal',
            min: 0,
            max: 18,
            decimal: 0,
            label: this.state.dict['header.form.decimal'],
            initVal: card.decimal,
            required: false
          },
          {
            type: 'select',
            key: 'format',
            label: this.state.dict['header.form.format'],
            initVal: card.format || '',
            options: [{
              MenuID: '',
              text: this.state.dict['header.form.empty']
            }, {
              MenuID: 'thdSeparator',
              text: this.state.dict['header.form.thdSeparator']
            }],
            required: false
          },
          {
            type: 'text',
            key: 'prefix',
            label: this.state.dict['header.form.prefix'],
            initVal: card.prefix || '',
            required: false,
            readonly: false
          },
          {
            type: 'text',
            key: 'postfix',
            label: this.state.dict['header.form.postfix'],
            initVal: card.postfix || '',
            // tooltip: '后缀值设置为"\\n",表示换行',
            tooltipClass: 'middle',
            required: false,
            readonly: false
          },
          {
            type: 'select',
            key: 'match',
            label: this.state.dict['header.form.match'],
            initVal: card.match || '',
            options: [{
              MenuID: '',
              text: this.state.dict['header.form.empty']
            }, {
              MenuID: '>',
              text: '>'
            }, {
              MenuID: '<',
              text: '<'
            }, {
              MenuID: '>=',
              text: '>='
            }, {
              MenuID: '<=',
              text: '<='
            }],
            required: false
          },
          {
            type: 'text',
            key: 'matchVal',
            min: -Infinity,
            max: Infinity,
            decimal: 0,
            label: this.state.dict['header.form.matchVal'],
            initVal: card.matchVal || '',
            required: false,
            readonly: false
          },
          {
            type: 'select',
            key: 'color',
            label: this.state.dict['header.form.color'],
            initVal: card.color || '',
            options: [{
              MenuID: '',
              text: this.state.dict['header.form.empty']
            }, {
              MenuID: 'red',
              text: '红色(内容)'
            }, {
              MenuID: 'redbg',
              text: '红色(背景)'
            }, {
              MenuID: 'orange',
              text: '橙色(内容)'
            }, {
              MenuID: 'orangebg',
              text: '橙色(背景)'
            }, {
              MenuID: 'green',
              text: '绿色(内容)'
            }, {
              MenuID: 'greenbg',
              text: '绿色(背景)'
            }],
            required: false
          }
        ]
      })
    } else {
      this.setState({
        visible: true,
        formtemp: 'columns',
        card: card
      })
    }
  }
  handleTab = (card) => {
    const { config } = this.state
    let menus = []
    config.tabs.forEach(item => {
      if (item.origin || card.uuid === item.uuid) return
      let menu = {
        value: item.uuid,
        text: item.label
      }
      menus.push(menu)
    })
    if (card.supMenu && card.supMenu !== 'mainTable') {
      let _menu = menus.filter(item => item.value === card.supMenu)[0]
      if (!_menu) {
        card.supMenu = ''
      }
    }
    this.setState({
      visible: true,
      formtemp: 'tabs',
      modalTitle: '编辑-标签页',
      card: card,
      formlist: [
        {
          type: 'text',
          key: 'label',
          label: this.state.dict['header.form.name'],
          initVal: card.label || '',
          required: true
        },
        {
          type: 'select',
          key: 'type',
          label: this.state.dict['header.form.type'],
          initVal: card.type || '',
          required: true,
          options: [{
            value: 'SubTable',
            text: this.state.dict['header.menu.tab.subtable']
          }]
        },
        {
          type: 'select',
          key: 'linkTab',
          label: '关联标签',
          initVal: card.linkTab || '',
          required: false,
          options: [{
            value: '',
            text: '新建'
          }, {
            value: 'jadisfjiasodjIjjaidfoasdf',
            text: '子表1'
          }, {
            value: 'dasjfsioafjiaga',
            text: '子表2'
          }, {
            value: 'jadsifjasgfisag',
            text: '子表3'
          }]
        },
        {
          type: 'select',
          key: 'icon',
          label: this.state.dict['header.menu.icon'],
          initVal: card.icon || '',
          required: false,
          options: [{
            value: '',
            text: this.state.dict['header.form.empty']
          }, {
            value: 'table',
            text: 'table'
          }, {
            value: 'bar-chart',
            text: 'bar-chart'
          }, {
            value: 'pie-chart',
            text: 'pie-chart'
          }, {
            value: 'line-chart',
            text: 'line-chart'
          }]
        },
        {
          type: 'select',
          key: 'supMenu',
          label: '关联菜单',
          initVal: card.supMenu || '',
          required: false,
          options: [
            {
              value: '',
              text: this.state.dict['header.form.empty']
            }, {
              value: 'mainTable',
              text: '主表'
            },
            ...menus
          ]
        }
      ]
    })
  }
  handleGridBtn = () => {
    this.setState({
      visible: true,
      formtemp: 'gridbtn',
      modalTitle: '编辑-操作列',
    })
  }
  /**
   * @description 搜索、按钮、显示列修改后提交保存
   * 1、搜索条件保存,当类型为下拉框且存在数据源时,将查询条件拼接为sql,并用base64转码
   * 2、按钮包括正常编辑和复制,复制时,按钮列末尾添加
   * 3、添加或编辑列,保存时,如按钮位置设置为表格,则修改操作列显示状态
   */
  handleSubmit = () => {
    const { menu } = this.props
    const { card } = this.state
    let _config = JSON.parse(JSON.stringify(this.state.config))
    if (this.state.formtemp !== 'gridbtn') {
      this.formRef.handleConfirm().then(res => {
        let isupdate = false
        if (res.type === 'search') {
          if ((res.values.type === 'select' || res.values.type === 'link') && res.values.resourceType === '1') {
            let sql = 'select ' + res.values.valueField + ',' + res.values.valueText + ' from ' + res.values.dataSource
            if (res.values.type === 'link') {
              sql = 'select ' + res.values.valueField + ',' + res.values.valueText + ',' + res.values.linkField + ' from ' + res.values.dataSource
            }
            if (res.values.orderBy) {
              sql = sql + ' order by ' + res.values.orderBy + ' ' + res.values.orderType
            }
            res.values.dataSourceSql = Utils.formatOptions(sql)
          }
        }
        if (res.type === 'action' && card.originCard && res.values.OpenType === 'pop') {
          Api.getSystemConfig({
            func: 'sPC_Get_LongParam',
            MenuID: card.originCard.uuid
          }).then(result => {
            if (result.status && result.LongParam) {
              let param = {
                func: 'sPC_ButtonParam_AddUpt',
                ParentID: menu.MenuID,
                MenuID: res.values.uuid,
                MenuNo: menu.MenuNo,
                Template: 'Modal',
                MenuName: res.values.label,
                PageParam: JSON.stringify({Template: 'Modal'}),
                LongParam: result.LongParam
              }
              Api.getSystemConfig(param).then(response => {
                if (!response.status) {
                  notification.warning({
                    top: 92,
                    message: response.message,
                    duration: 10
                  })
                }
              })
            }
          })
        }
        _config[res.type] = _config[res.type].map(item => {
          if (item.uuid === res.values.uuid) {
            isupdate = true
            return res.values
          } else {
            return item
          }
        })
        _config[res.type] = _config[res.type].filter(item => !item.origin)
        if (!isupdate) { // 操作不是修改,添加元素至列表
          _config[res.type].push(res.values)
        }
        if (res.type === 'action') {
          let gridbtn = _config.action.filter(act => act.position === 'grid')
          let _display = false
          if (gridbtn.length > 0) {
            _display = true
          }
          if (_config.gridBtn) {
            _config.gridBtn.display = _display
          } else {
            _config.gridBtn = {
              display: _display,
              Align: 'center',
              IsSort: 'false',
              uuid: Utils.getuuid(),
              label: this.state.dict['header.form.column.action'],
              type: 'action',
              style: 'button',
              show: 'horizontal',
              Width: 120
            }
          }
        }
        this.setState({
          config: _config,
          searchloading: true,
          actionloading: true,
          columnsloading: true,
          tabloading: true,
          visible: false
        }, () => {
          this.setState({
            searchloading: false,
            actionloading: false,
            columnsloading: false,
            tabloading: false
          })
        })
      })
    } else {
      this.formRef.handleConfirm().then(res => {
        _config.gridBtn = res
        this.setState({
          config: _config,
          visible: false
        })
      })
    }
  }
  /**
   * @description 创建按钮存储过程
   */
  creatFunc = () => {
    const { menu } = this.props
    let _config = JSON.parse(JSON.stringify(this.state.config))
    this.formRef.handleConfirm().then(res => {
      let btn = res.values  // 按钮信息
      let newLText = ''     // 创建存储过程sql
      let DelText = ''      // 删除存储过程sql
      let isExit = false    // 存储过程是否存在
      let sysTVPText = ''   // 已有的存储过程语句(云端)
      let localTVPText = '' // 已有的存储过程语句(本地)
      // 创建存储过程,必须填写内部函数名
      if (!btn.innerFunc) {
        notification.warning({
          top: 92,
          message: '请填写内部函数!',
          duration: 10
        })
        return
      }
      // 创建中
      this.setState({
        funcLoading: true
      })
      new Promise(resolve => {
        // 弹窗(表单)类按钮,先获取按钮配置信息,如果尚未配置按钮则会报错并终止。
        // 获取信息后生成删除和创建存储过程的语句
        if (btn.OpenType === 'pop') {
          Api.getSystemConfig({
            func: 'sPC_Get_LongParam',
            MenuID: btn.uuid
          }).then(res => {
            let _LongParam = ''
            if (res.status && res.LongParam) {
              _LongParam = window.decodeURIComponent(window.atob(res.LongParam))
              try {
                _LongParam = JSON.parse(_LongParam)
              } catch (e) {
                _LongParam = ''
              }
            }
            if (_LongParam) {
              let fields = []
              if (_LongParam.groups.length > 0) {
                _LongParam.groups.forEach(group => {
                  fields = [...fields, ...group.sublist]
                })
              } else {
                fields = _LongParam.fields
              }
              let _param = {
                funcName: btn.innerFunc,
                name: _config.setting.tableName || '',
                fields: fields,
                menuNo: menu.MenuNo
              }
              newLText = Utils.formatOptions(Utils.getfunc(_param, btn, menu, _config.columns))
              DelText = Utils.formatOptions(Utils.dropfunc(_param.funcName))
              resolve(true)
            } else {
              resolve(false)
              notification.warning({
                top: 92,
                message: '弹窗(表单)按钮,请先配置表单信息!',
                duration: 10
              })
            }
          })
        } else {
          let _param = {
            funcName: btn.innerFunc,
            name: _config.setting.tableName || '',
            fields: '',
            menuNo: menu.MenuNo
          }
          newLText = Utils.formatOptions(Utils.getfunc(_param, btn, menu, _config.columns))
          DelText = Utils.formatOptions(Utils.dropfunc(_param.funcName))
          resolve(true)
        }
      }).then(res => {
        // 获取云端及本地,是否已存在该存储过程的信息
        if (res === false) return res
        let sysDefer = new Promise(resolve => {
          Api.getSystemConfig({
            func: 'sPC_Get_TVP', // 云端获取存储结果
            TVPName: btn.innerFunc
          }).then(result => {
            resolve(result)
          })
        })
        let localDefer = new Promise(resolve => {
          let _param = { // 获取本地存储过程信息
            func: 's_get_userproc',
            LText: btn.innerFunc
          }
          _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
          _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
          Api.getLocalConfig(_param).then(result => {
            resolve(result)
          })
        })
        return Promise.all([sysDefer, localDefer])
      }).then(res => {
        // 云端结果与新语句不同时,更新云端信息
        if (res === false) return res
        let isError = false
        res.forEach((result, index) => {
          if (!result.status) {
            notification.warning({
              top: 92,
              message: result.message,
              duration: 10
            })
            isError = true
          } else if (index === 0) {
            sysTVPText = result.TVPText
          } else {
            if (result.Ltext) { // 本地存储过程是否存在
              isExit = true
            }
            localTVPText = Utils.formatOptions(result.Ltext)
          }
        })
        if (isError) return false
        if ((newLText === localTVPText) && (newLText === sysTVPText)) {
          return 'drop'
        } else if (!localTVPText || (localTVPText === sysTVPText)) {
          // 本地存储过程不存在,将新的存储过程更新至云端
          return Api.getSystemConfig({
            func: 'sPC_TVP_InUp',
            TVPName: btn.innerFunc,
            TVPText: newLText,
            TypeName: 'P'
          })
        } else {
          return new Promise(resolve => {
            Api.getSystemConfig({ // 添加现有的本地存储过程至云端
              func: 'sPC_TVP_InUp',
              TVPName: btn.innerFunc,
              TVPText: localTVPText,
              TypeName: 'P'
            }).then(result => {
              if (result.status) {
                Api.getSystemConfig({
                  func: 'sPC_TVP_InUp', // 添加最新的存储过程至云端
                  TVPName: btn.innerFunc,
                  TVPText: newLText,
                  TypeName: 'P'
                }).then(response => {
                  resolve(response)
                })
              } else {
                resolve(result)
              }
            })
          })
        }
      }).then(res => {
        // 云端信息更新后,判断是删除或是直接新建存储过程
        if (res === false || res === 'drop') return res
        if (!res.status) {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
          return false
        } else if (isExit) {
          return 'drop'
        } else {
          return 'create'
        }
      }).then(res => {
        // 删除存储过程
        if (res === false || res === 'create') return res
        let _param = {
          func: 'sPC_TableData_InUpDe',
          LText: DelText,
          TypeCharOne: 'proc' // 删除或创建存储过程
        }
        _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
        return Api.getLocalConfig(_param)
      }).then(res => {
        // 根据上述操作结果,判断是否新建存储过程
        if (res === false || res === 'create') return res
        if (!res.status) {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
          return false
        } else {
          return 'create'
        }
      }).then(res => {
        // 新建存储过程
        if (res === false) return res
        let _param = {
          func: 'sPC_TableData_InUpDe',
          LText: newLText,
          TypeCharOne: 'proc' // 删除或创建存储过程
        }
        _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
        return Api.getLocalConfig(_param)
      }).then(res => {
        // 处理新建结果
        if (res === false) return res
        if (!res.status) {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
          return false
        } else {
          notification.success({
            top: 92,
            message: '创建成功',
            duration: 2
          })
          return true
        }
      }).then(res => {
        // 新建成功后,更新页面按钮信息
        if (res === false) {
          this.setState({
            funcLoading: false
          })
          return
        }
        let isupdate = false
        _config.action = _config.action.map(item => {
          if (item.uuid === btn.uuid) {
            isupdate = true
            return btn
          } else {
            return item
          }
        })
        _config.action = _config.action.filter(item => !item.origin)
        if (!isupdate) { // 操作不是修改,添加元素至列表
          _config.action.push(btn)
        }
        let gridbtn = _config.action.filter(act => act.position === 'grid')
        let _display = false
        if (gridbtn.length > 0) {
          _display = true
        }
        if (_config.gridBtn) {
          _config.gridBtn.display = _display
        } else {
          _config.gridBtn = {
            display: _display,
            Align: 'center',
            IsSort: 'false',
            uuid: Utils.getuuid(),
            label: this.state.dict['header.form.column.action'],
            type: 'action',
            style: 'button',
            show: 'horizontal',
            Width: 120
          }
        }
        this.setState({
          config: _config,
          actionloading: true,
          funcLoading: false
        }, () => {
          this.setState({
            actionloading: false
          })
        })
      })
    })
  }
  /**
   * @description 创建表格存储过程
   */
  tableCreatFunc = () => {
    const { menu } = this.props
    let config = JSON.parse(JSON.stringify(this.state.config))
    this.settingRef.handleConfirm().then(res => {
      const setting = res
      if (!(setting.interType === 'inner') || !setting.innerFunc) {
        notification.warning({
          top: 92,
          message: '接口类型为-内部,且存在内部函数时,才可以创建存储过程!',
          duration: 10
        })
        return
      }
      if (setting.dataresource.length > 50 && config.setting.dataresource !== setting.dataresource) {
        let param = {
          func: 's_DataSrc_Save',
          LText: setting.dataresource,
          MenuID: menu.MenuID
        }
        param.LText = Utils.formatOptions(param.LText)
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        param.secretkey = Utils.encrypt(param.LText, param.timestamp)
        Api.getLocalConfig(param)
      }
      this.setState({
        funcLoading: true
      })
      let newLText = Utils.formatOptions(Utils.getTableFunc(setting, menu, config)) // 创建存储过程sql
      let DelText = Utils.formatOptions(Utils.dropfunc(setting.innerFunc))          // 删除存储过程sql
      new Promise(resolve => {
        let sysDefer = new Promise(resolve => {
          Api.getSystemConfig({
            func: 'sPC_Get_TVP', // 云端获取存储结果
            TVPName: setting.innerFunc
          }).then(result => {
            if (!result.status) {
              notification.warning({
                top: 92,
                message: result.message,
                duration: 10
              })
              resolve(false)
            } else {
              resolve(result)
            }
          })
        })
        let localDefer = new Promise(resolve => {
          let _param = { // 获取本地存储过程信息
            func: 's_get_userproc',
            LText: setting.innerFunc
          }
          _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
          _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
          Api.getLocalConfig(_param).then(result => {
            if (!result.status) {
              notification.warning({
                top: 92,
                message: result.message,
                duration: 10
              })
              resolve(false)
            } else {
              resolve(result)
            }
          })
        })
        Promise.all([sysDefer, localDefer]).then(result => {
          resolve(result)
        })
      }).then(res => {
        // 获取云端及本地,是否已存在该存储过程的信息
        if (res === false) return res
        if (res[0] === false || res[1] === false) return false
        let cloudfunc = ''
        let localfunc = ''
        res.forEach((item, index) => {
          if (index === 0 && item.TVPText) {
            cloudfunc = item.TVPText
          } else if (index === 1 && item.Ltext) {
            localfunc = Utils.formatOptions(item.Ltext)
          }
        })
        if ((newLText === localfunc) && (newLText === cloudfunc)) {
          return 'drop'
        } else if (!localfunc || (cloudfunc === localfunc)) {
          // 本地存储过程不存在,或云端和本地存储过程一致时,将新的存储过程更新至云端
          return Api.getSystemConfig({
            func: 'sPC_TVP_InUp',
            TVPName: setting.innerFunc,
            TVPText: newLText,
            TypeName: 'P'
          })
        } else {
          return new Promise(resolve => {
            Api.getSystemConfig({ // 添加现有的本地存储过程至云端
              func: 'sPC_TVP_InUp',
              TVPName: setting.innerFunc,
              TVPText: localfunc,
              TypeName: 'P'
            }).then(result => {
              if (result.status) {
                Api.getSystemConfig({
                  func: 'sPC_TVP_InUp', // 添加最新的存储过程至云端
                  TVPName: setting.innerFunc,
                  TVPText: newLText,
                  TypeName: 'P'
                }).then(response => {
                  resolve(response)
                })
              } else {
                resolve(result)
              }
            })
          })
        }
      }).then(res => {
        // 云端信息更新后,判断是删除或是直接新建存储过程
        if (res === false || res === 'drop') return res
        if (!res.status) {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
          return false
        } else {
          return 'create'
        }
      }).then(res => {
        // 删除存储过程
        if (res === false || res === 'create') return res
        let _param = {
          func: 'sPC_TableData_InUpDe',
          LText: DelText,
          TypeCharOne: 'proc' // 删除或创建存储过程
        }
        _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
        return Api.getLocalConfig(_param)
      }).then(res => {
        // 根据上述操作结果,判断是否新建存储过程
        if (res === false || res === 'create') return res
        if (!res.status) {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
          return false
        } else {
          return 'create'
        }
      }).then(res => {
        // 新建存储过程
        if (res === false) return res
        let _param = {
          func: 'sPC_TableData_InUpDe',
          LText: newLText,
          TypeCharOne: 'proc' // 删除或创建存储过程
        }
        _param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
        return Api.getLocalConfig(_param)
      }).then(res => {
        // 处理新建结果
        if (res === false) return res
        if (!res.status) {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
          return false
        } else {
          notification.success({
            top: 92,
            message: '创建成功',
            duration: 2
          })
          return true
        }
      }).then(res => {
        // 新建成功后,更新页面按钮信息
        if (res === false) {
          this.setState({
            funcLoading: false
          })
          return
        }
        this.setState({
          config: {...config, setting: setting}
        })
      })
    })
  }
  deleteElement = (element) => {
    let _this = this
    confirm({
      content: `确定删除<<${element.card.label}>>吗?`,
      okText: this.state.dict['header.confirm'],
      cancelText: this.state.dict['header.cancel'],
      onOk() {
        let _config = JSON.parse(JSON.stringify(_this.state.config))
        _config[element.type] = _config[element.type].filter(item => {
          if (item.uuid === element.card.uuid) {
            return false
          } else {
            return true
          }
        })
        let refreshtype = element.type + 'loading'
        if (/^tab/.test(refreshtype)) {
          refreshtype = 'tabloading'
        }
        _this.setState({
          config: _config,
          delActions: [..._this.state.delActions, element.card.uuid],
          [refreshtype]: true
        }, () => {
          _this.setState({
            [refreshtype]: false
          })
        })
      },
      onCancel() {}
    })
  }
  changeTemplate = () => {
    this.props.handleConfig('template')
  }
  /**
   * @description 三级菜单保存
   */
  submitConfig = () => {
    const { menu } = this.props
    const { originMenu } = this.state
    let config = JSON.parse(JSON.stringify(this.state.config))
    this.menuformRef.handleConfirm().then(res => {
      if (config.search[0] && config.search[0].origin) {
        config.search = config.search.filter(item => !item.origin)
      }
      if (config.action[0] && config.action[0].origin) {
        config.action = config.action.filter(item => !item.origin)
      }
      if (config.columns[0] && config.columns[0].origin) {
        config.columns = config.columns.filter(item => !item.origin)
      }
      if (config.tabs[0] && config.tabs[0].origin) {
        config.tabs = config.tabs.filter(item => !item.origin)
      }
      let _LongParam = ''
      let _config = {...config, tables: this.state.selectedTables}
      let _pageParam = {...menu.PageParam, OpenType: res.opentype}
      // 保存时删除配置类型,system 、user
      delete _config.type
      try {
        _LongParam = window.btoa(window.encodeURIComponent(JSON.stringify(_config)))
      } catch (e) {
        notification.warning({
          top: 92,
          message: '编译错误',
          duration: 10
        })
        return
      }
      let btnParam = {
        func: 'sPC_Button_AddUpt',
        ParentID: menu.MenuID,
        MenuNo: res.menuNo,
        Template: menu.PageParam.Template || '',
        PageParam: '',
        LongParam: '',
        LText: config.action.map((item, index) => {
          return `select '${item.uuid}' as menuid, '${item.label}' as menuname, '${(index + 1) * 10}' as Sort`
        })
      }
      btnParam.LText = btnParam.LText.join(' union all ')
      btnParam.LText = Utils.formatOptions(btnParam.LText)
      btnParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
      btnParam.secretkey = Utils.encrypt(btnParam.LText, btnParam.timestamp)
      let param = {
        func: 'sPC_TrdMenu_AddUpt',
        ParentID: res.parentId,
        MenuID: menu.MenuID,
        MenuNo: res.menuNo,
        Template: menu.PageParam.Template || '',
        MenuName: res.menuName,
        Sort: (this.props.supMenuList.length + 1) * 10,
        PageParam: JSON.stringify(_pageParam),
        LongParam: _LongParam
      }
      if (this.state.closeVisible) { // 显示关闭对话框时,模态框中保存按钮,显示保存中状态
        this.setState({
          menucloseloading: true
        })
      } else {
        this.setState({
          menuloading: true
        })
      }
      Api.getSystemConfig(param).then(response => {
        if (response.status) {
          this.setState({
            config: _config,
            originMenu: {
              ...originMenu,
              LongParam: _config,
              PageParam: _pageParam,
              MenuName: res.menuName,
              MenuNo: res.menuNo,
              ParentID: res.parentId
            },
            searchloading: true,
            actionloading: true,
            columnsloading: true
          }, () => {
            this.setState({
              searchloading: false,
              actionloading: false,
              columnsloading: false
            })
          })
          this.props.reloadmenu()
          this.submitAction(btnParam)
        } else {
          this.setState({
            menuloading: false,
            menucloseloading: false
          })
          notification.warning({
            top: 92,
            message: response.message,
            duration: 10
          })
        }
      })
    }, () => {
      notification.warning({
        top: 92,
        message: this.state.dict['header.menu.basemsg'],
        duration: 10
      })
    })
  }
  /**
   * @description 保存或修改菜单按钮
   */
  submitAction = (param) => {
    const { config } = this.state
    new Promise(resolve => {
      // 内部请求
      if (this.state.delActions.length > 0) {
        let deffers = this.state.delActions.map(item => {
          let _param = {
            func: 'sPC_MainMenu_Del',
            MenuID: item
          }
          return new Promise(resolve => {
            Api.getSystemConfig(_param).then(res => {
              resolve(res)
            })
          })
        })
        Promise.all(deffers).then(result => {
          let error = false
          result.forEach(res => {
            if (!res.status) {
              error = res
            }
          })
          if (error) {
            notification.warning({
              top: 92,
              message: error.message,
              duration: 10
            })
            resolve(false)
          } else {
            this.setState({
              delActions: []
            })
            resolve(param)
          }
        })
      } else if (this.state.delActions.length === 0) {
        resolve(param)
      }
    }).then(res => {
      if (res === false) return res
      if (res.LText) {
        return Api.getSystemConfig(res)
      } else {
        return 'copy'
      }
    }).then(response => {
      if (response === false || response === 'copy') return response
      if (response.status) {
        return 'copy'
      } else {
        notification.warning({
          top: 92,
          message: response.message,
          duration: 10
        })
        return false
      }
    }).then(response => {
      if (response === false) return response
      let oriActions = []
      this.state.originActions.forEach(item => {
        let curBtn = config.action.filter(cell => item.curuuid === cell.uuid)[0] // 查看初始化按钮是否存在
        if (!curBtn) return
        if (curBtn.OpenType !== item.prebtn.OpenType) return
        oriActions.push({
          prebtn: item.prebtn,
          curBtn: curBtn
        })
      })
      if (oriActions.length === 0) return 'true'
      oriActions.forEach(action => {
        Api.getSystemConfig({
          func: 'sPC_Get_LongParam',
          MenuID: action.prebtn ? action.prebtn.uuid : ''
        }).then(result => {
          if (result.status && result.LongParam) {
            let _LongParam = ''
            if (result.LongParam) {
              _LongParam = window.decodeURIComponent(window.atob(result.LongParam))
              try {
                _LongParam = JSON.parse(_LongParam)
              } catch (e) {
                _LongParam = ''
              }
            }
            if (_LongParam) {
              let param = {
                func: 'sPC_ButtonParam_AddUpt',
                ParentID: this.props.menu.MenuID,
                MenuID: action.curBtn.uuid,
                MenuNo: this.props.menu.MenuNo,
                Template: _LongParam.type,
                MenuName: action.curBtn.label,
                PageParam: JSON.stringify({Template: _LongParam.type}),
                LongParam: result.LongParam
              }
              Api.getSystemConfig(param).then(() => {})
            }
          }
        })
      })
      return 'true'
    }).then(response => {
      if (response === 'true') {
        notification.success({
          top: 92,
          message: '保存成功',
          duration: 2
        })
        if (this.state.closeVisible) {
          this.props.handleConfig('')
        } else {
          this.setState({
            menuloading: false,
            menucloseloading: false
          })
        }
      } else {
        this.setState({
          menuloading: false,
          menucloseloading: false
        })
      }
    })
  }
  cancelConfig = () => {
    const { menu } = this.props
    const { config, originMenu } = this.state
    let _this = this
    let isAdd = false
    if (
      (config.search[0] && config.search[0].origin) ||
      (config.action[0] && config.action[0].origin) ||
      (config.columns[0] && config.columns[0].origin) ||
      (config.tabs[0] && config.tabs[0].origin)
    ) {
      isAdd = true
    }
    if (isAdd) {
      confirm({
        content: '菜单尚未提交,确定放弃保存吗?',
        okText: this.state.dict['header.confirm'],
        cancelText: this.state.dict['header.cancel'],
        onOk() {
          _this.props.handleConfig('')
        },
        onCancel() {}
      })
    } else {
      this.menuformRef.handleConfirm().then(res => {
        let _config = {...config, tables: this.state.selectedTables}
        let _pageParam = {...menu.PageParam, OpenType: res.opentype}
        let _originMenu = {
          ...originMenu,
          LongParam: _config,
          PageParam: _pageParam,
          MenuName: res.menuName,
          MenuNo: res.menuNo,
          ParentID: res.parentId
        }
        if (!is(fromJS(originMenu), fromJS(_originMenu))) {
          this.setState({
            closeVisible: true
          })
        } else {
          this.props.handleConfig('')
        }
      }, () => {
        this.setState({
          closeVisible: true
        })
      })
    }
  }
  queryField = (type) => {
    const {selectedTables, tableColumns, config} = this.state
    // 判断是否已选择表名
    if (selectedTables.length === 0) {
      notification.warning({
        top: 92,
        message: '请选择表名!',
        duration: 10
      })
      return
    }
    // 表字段集转为map数据
    let columns = new Map()
    tableColumns.forEach(table => {
      table.columns.forEach(column => {
        columns.set(column.field, column)
      })
    })
    if (type === 'search') {
      // 添加搜索条件,字段集中存在搜索条件字段,使用搜索条件对象替换字段集,设置数据类型
      config.search.forEach(item => {
        if (columns.has(item.field)) {
          let _datatype = columns.get(item.field).datatype
          columns.set(item.field, {...item, selected: true, datatype: _datatype})
        }
      })
    } else if (type === 'columns') {
      // 添加显示列,字段集中存在显示列字段,使用显示列对象替换字段集,设置数据类型
      config.columns.forEach(item => {
        if (columns.has(item.field)) {
          let _datatype = columns.get(item.field).datatype
          columns.set(item.field, {...item, selected: true, datatype: _datatype})
        }
      })
    }
    // 显示字段集弹窗
    this.setState({
      addType: type,
      tableVisible: true,
      fields: [...columns.values()]
    })
  }
  addFieldSubmit = () => {
    // 字段集为空,关闭弹窗
    if (!this.state.fields || this.state.fields.length === 0) {
      this.setState({
        tableVisible: false,
        addType: ''
      })
    }
    const {addType, config} = this.state
    const textmatch = { // 选择text时匹配规则
      text: 'like',
      number: 'like',
      datetime: 'like',
      date: 'like'
    }
    const selectmatch = { // 选择select时匹配规则
      text: '=',
      number: '=',
      datetime: '=',
      date: '='
    }
    const datematch = { // 选择dateRange时匹配规则
      text: 'between',
      number: 'between',
      datetime: 'between',
      date: 'between'
    }
    // 获取已选字段集合
    let cards = this.refs.searchcard.state.selectCards
    let columnsMap = new Map()
    cards.forEach(card => {
      columnsMap.set(card.field, card)
    })
    let items = []
    if (addType === 'search') {
      config.search.forEach(item => {
        if (columnsMap.has(item.field)) {
          let cell = columnsMap.get(item.field)
          if (cell.selected && cell.type === item.type) { // 数据未修改
            items.push(item)
          } else if (cell.selected) { // 数据类型修改
            if (cell.type === 'text') {
              item.match = textmatch[cell.datatype]
            } else if (cell.type === 'select') {
              item.match = selectmatch[cell.datatype]
            } else if (cell.type === 'daterange') {
              item.match = datematch[cell.datatype]
            } else {
              cell.type = 'text'
              item.match = textmatch[cell.datatype]
            }
            item.type = cell.type
            item.initval = ''
            items.push(item)
          }
          columnsMap.delete(item.field)
        } else if (!item.origin) {
          items.push(item)
        }
      })
      let _columns = [...columnsMap.values()]
      _columns.forEach(item => {
        if (item.selected) {
          let _match = ''
          if (item.type === 'text') {
            _match = textmatch[item.datatype]
          } else if (item.type === 'select') {
            _match = selectmatch[item.datatype]
          } else if (item.type === 'daterange') {
            _match = datematch[item.datatype]
          } else {
            item.type = 'text'
            _match = textmatch[item.datatype]
          }
          let newcard = {
            uuid: Utils.getuuid(),
            label: item.label,
            field: item.field,
            initval: '',
            type: item.type,
            resourceType: '0',
            setAll: 'false',
            options: [],
            dataSource: '',
            linkField: '',
            valueField: '',
            valueText: '',
            orderBy: '',
            orderType: 'asc',
            match: _match,
            display: 'dropdown'
          }
          items.push(newcard)
        }
      })
    } else {
      config.columns.forEach(item => {
        if (columnsMap.has(item.field)) {
          let cell = columnsMap.get(item.field)
          if (cell.selected) {
            items.push(item)
          }
          columnsMap.delete(item.field)
        } else if (!item.origin) {
          items.push(item)
        }
      })
      let _columns = [...columnsMap.values()]
      _columns.forEach(item => {
        if (item.selected) {
          let newcard = {
            uuid: Utils.getuuid(),
            Align: 'left',
            label: item.label,
            field: item.field,
            Hide: 'false',
            IsSort: item.type === 'picture' ? 'false' : 'true',
            type: item.type,
            Width: 120
          }
          items.push(newcard)
        }
      })
    }
    this.setState({
      [addType + 'loading']: true,
      config: {...config, [addType]: items}
    }, () => {
      notification.success({
        top: 92,
        message: '操作成功',
        duration: 2
      })
      this.setState({
        [addType + 'loading']: false
      })
    })
  }
  onTableChange = (value) => {
    const {tables, selectedTables, tableColumns} = this.state
    let _table = tables.filter(item => item.TbName === value)[0]
    let isSelected = !!selectedTables.filter(cell => cell.TbName === value)[0]
    if (!isSelected) {
      this.setState({
        selectedTables: [...selectedTables, _table]
      })
      Api.getSystemConfig({func: 'sPC_Get_FieldName', TBName: value}).then(res => {
        if (res.status) {
          let tabmsg = {
            tableName: _table.name,
            columns: res.FDName.map(item => {
              let _type = item.FieldType.toLowerCase()
              let _decimal = 0
              if (/^nvarchar/.test(_type)) {
                _type = 'text'
              } else if (/^int/.test(_type)) {
                _type = 'number'
              } else if (/^decimal/.test(_type)) {
                _decimal = _type.split(',')[1]
                _decimal = parseInt(_decimal)
                _type = 'number'
              } else if (/^datetime/.test(_type)) {
                _type = 'datetime'
              } else if (/^date/.test(_type)) {
                _type = 'date'
              } else {
                _type = 'text'
              }
              return {
                field: item.FieldName,
                label: item.FieldDec,
                type: _type,
                datatype: _type,
                decimal: _decimal
              }
            })
          }
          this.setState({
            tableColumns: [...tableColumns, tabmsg]
          })
        } else {
          notification.warning({
            top: 92,
            message: res.message,
            duration: 10
          })
        }
      })
    }
  }
  deleteTable = (table) => {
    const {selectedTables, tableColumns} = this.state
    this.setState({
      selectedTables: selectedTables.filter(item => item.TbName !== table.TbName),
      tableColumns: tableColumns.filter(item => item.tableName !== table.TbName)
    })
  }
  changeSetting = () => {
    this.setState({
      settingVisible: true
    })
  }
  settingSave = () => {
    const { menu } = this.props
    const {config} = this.state
    this.settingRef.handleConfirm().then(res => {
      if (
        res.interType === 'inner' &&
        !res.innerFunc &&
        res.dataresource.length > 50 &&
        config.setting.dataresource !== res.dataresource
      ) {
        let param = {
          func: 's_DataSrc_Save',
          LText: res.dataresource,
          MenuID: menu.MenuID
        }
        param.LText = Utils.formatOptions(param.LText)
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        param.secretkey = Utils.encrypt(param.LText, param.timestamp)
        Api.getLocalConfig(param)
      }
      this.setState({
        config: {...config, setting: res},
        settingVisible: false,
        columnsloading: true
      }, () => {
        this.setState({
          columnsloading: false
        })
      })
    })
  }
  /**
   * @description 设置可配置按钮
   */
  setSubConfig = (btn, type) => {
    const { menu } = this.props
    const { config, originMenu } = this.state
    let isAdd = false
    if (
      (config.search[0] && config.search[0].origin) ||
      (config.action[0] && config.action[0].origin) ||
      (config.columns[0] && config.columns[0].origin) ||
      (config.tabs[0] && config.tabs[0].origin)
    ) {
      isAdd = true
    }
    if (isAdd) {
      notification.warning({
        top: 92,
        message: '菜单尚未保存,请保存菜单配置!',
        duration: 10
      })
    } else {
      this.menuformRef.handleConfirm().then(res => {
        let _config = {...config, tables: this.state.selectedTables}
        let _pageParam = {...menu.PageParam, OpenType: res.opentype}
        let _originMenu = {
          ...originMenu,
          LongParam: _config,
          PageParam: _pageParam,
          MenuName: res.menuName,
          MenuNo: res.menuNo,
          ParentID: res.parentId
        }
        if (!is(fromJS(originMenu), fromJS(_originMenu))) {
          notification.warning({
            top: 92,
            message: '菜单配置已修改,请保存!',
            duration: 10
          })
        } else if (type === 'button') {
          this.setState({
            loading: true
          })
          Api.getSystemConfig({
            func: 'sPC_Get_LongParam',
            MenuID: btn.uuid
          }).then(res => {
            if (res.status) {
              this.setState({
                loading: false
              })
              let _LongParam = ''
              if (res.LongParam) {
                _LongParam = window.decodeURIComponent(window.atob(res.LongParam))
                try {
                  _LongParam = JSON.parse(_LongParam)
                } catch (e) {
                  _LongParam = ''
                }
              }
              this.props.handleSubConfig(btn, originMenu, _LongParam, type)
            } else {
              this.setState({
                loading: false
              })
              notification.warning({
                top: 92,
                message: res.message,
                duration: 10
              })
            }
          })
        } else if (type === 'tab') {
          this.props.handleSubConfig(btn, originMenu, '', type)
          // this.setState({
          //   loading: true
          // })
          // Api.getSystemConfig({
          //   func: 'sPC_Get_LongParam',
          //   MenuID: btn.uuid
          // }).then(res => {
          //   if (res.status) {
          //     this.setState({
          //       loading: false
          //     })
          //     let _LongParam = ''
          //     if (res.LongParam) {
          //       _LongParam = window.decodeURIComponent(window.atob(res.LongParam))
          //       try {
          //         _LongParam = JSON.parse(_LongParam)
          //       } catch (e) {
          //         _LongParam = ''
          //       }
          //     }
          //     this.props.handleSubConfig(btn, originMenu, _LongParam)
          //   } else {
          //     this.setState({
          //       loading: false
          //     })
          //     notification.warning({
          //       top: 92,
          //       message: res.message,
          //       duration: 10
          //     })
          //   }
          // })
        }
      }, () => {
        notification.warning({
          top: 92,
          message: '菜单基本信息已修改,请保存!',
          duration: 10
        })
      })
    }
  }
  onEnabledChange = () => {
    const { config } = this.state
    this.setState({
      config: {...config, enabled: !config.enabled}
    })
  }
  onColumnNameChange = () => {
    const { showColumnName } = this.state
    this.setState({
      showColumnName: !showColumnName
    })
  }
  render () {
    const configAction = this.state.config.action.filter(_action =>
      !_action.origin && (_action.OpenType === 'pop' || _action.OpenType === 'popview' || _action.OpenType === 'blank' || _action.OpenType === 'tab')
    )
    return (
      <div className="common-table-board">
        <DndProvider backend={HTML5Backend}>
          {/* 工具栏 */}
          <div className="tools">
            <Collapse accordion defaultActiveKey="0" bordered={false}>
              {/* 基本信息 */}
              <Panel header={this.state.dict['header.menu.basedata']} key="0" id="common-basedata">
                {/* 菜单信息 */}
                <MenuForm
                  dict={this.state.dict}
                  formlist={this.state.menuformlist}
                  wrappedComponentRef={(inst) => this.menuformRef = inst}
                />
                {/* 表名添加 */}
                <div className="ant-col ant-form-item-label">
                  <label>
                    <Tooltip placement="topLeft" title="此处可以添加配置相关的常用表,在添加搜索条件和显示列时,可通过工具栏中的添加按钮,批量添加表格相关字段。">
                      <Icon type="question-circle" />
                      {this.state.dict['header.menu.table.add']}
                    </Tooltip>
                  </label>
                </div>
                <Select
                  showSearch
                  className="tables"
                  style={{ width: '100%' }}
                  optionFilterProp="children"
                  value={this.state.dict['header.menu.table.placeholder']}
                  onChange={this.onTableChange}
                  showArrow={false}
                  getPopupContainer={() => document.getElementById('common-basedata')}
                  filterOption={(input, option) => {
                    return option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
                      option.props.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                  }}
                >
                  {this.state.tables.map((table, index) => (
                    <Option key={index} value={table.TbName}>{table.Remark}</Option>
                  ))}
                </Select>
                {this.state.selectedTables.length > 0 && <List
                  size="small"
                  bordered
                  dataSource={this.state.selectedTables}
                  renderItem={(item, index) => <List.Item key={index} title={item.Remark + ' (' + item.TbName + ')'}>
                    {item.Remark + ' (' + item.TbName + ')'}
                    <Icon type="close" onClick={() => this.deleteTable(item)}/>
                    <div className="bottom-mask"></div>
                  </List.Item>}
                />}
              </Panel>
              {/* 搜索条件添加 */}
              <Panel header={this.state.dict['header.menu.search']} key="1">
                <div className="search-element">
                  {Source.searchItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                <Button type="primary" block onClick={() => this.queryField('search')}>{this.state.dict['header.menu.search.add']}</Button>
              </Panel>
              {/* 按钮添加 */}
              <Panel header={this.state.dict['header.menu.action']} key="2">
                <div className="search-element">
                  {Source.actionItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                {configAction.length > 0 ?
                  <p className="config-btn-title">
                    <Tooltip placement="topLeft" title="点击按钮,可完成或查看按钮配置信息。">
                      <Icon type="question-circle" />
                    </Tooltip>
                    {this.state.dict['header.menu.action.configurable']}
                  </p> : null
                }
                {configAction.map((item, index) => {
                  return (
                    <div key={index}>
                      <Button
                        icon={item.icon}
                        style={{marginBottom: '10px'}}
                        className={'mk-btn mk-' + item.class}
                        onClick={() => this.setSubConfig(item, 'button')}
                      >{item.label}</Button>
                    </div>
                  )
                })}
              </Panel>
              {/* 添加显示列 */}
              <Panel header={this.state.dict['header.menu.column']} key="3">
                <div className="search-element">
                  {Source.columnItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                <Button type="primary" block onClick={() => this.queryField('columns')}>{this.state.dict['header.menu.column.add']}</Button>
              </Panel>
              {/* 添加标签 */}
              <Panel header={this.state.dict['header.menu.tab']} key="4">
                <div className="search-element">
                  {Source.tabItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                {this.state.config.tabs.length > 0 ?
                  <p className="config-btn-title">
                    <Tooltip placement="topLeft" title="点击按钮,可完成或查看标签配置信息。">
                      <Icon type="question-circle" />
                    </Tooltip>
                    {this.state.dict['header.menu.tab.configurable']}
                  </p> : null
                }
                {this.state.config.tabs.map((item, index) => {
                  return (
                    <div key={index}>
                      <Button
                        icon={item.icon}
                        style={{marginBottom: '10px'}}
                        onClick={() => this.setSubConfig(item, 'tab')}
                      >{item.label}</Button>
                    </div>
                  )
                })}
              </Panel>
            </Collapse>
          </div>
          <div className="setting">
            <Card title={this.state.dict['header.menu.page.configurable']} bordered={false} extra={
              <div>
                <Switch className="big" checkedChildren="启" unCheckedChildren="停" defaultChecked={this.state.config.enabled} onChange={this.onEnabledChange} />
                <Button type="primary" onClick={this.changeTemplate}>{this.state.dict['header.menu.template.change']}</Button>
                <Button type="primary" onClick={this.submitConfig} loading={this.state.menuloading}>{this.state.dict['header.save']}</Button>
                <Button onClick={this.cancelConfig}>{this.state.dict['header.return']}</Button>
              </div>
            } style={{ width: '100%' }}>
              <Icon type="setting" onClick={this.changeSetting} />
              <div className="search-list">
                <Tooltip placement="bottomLeft" overlayClassName="middle" title="在左侧工具栏《搜索》中,选择对应搜索框拖至此处添加;或点击按钮《添加搜索条件》批量添加,选择批量添加时,需提前选择使用表。">
                  <Icon type="question-circle" />
                </Tooltip>
                {!this.state.searchloading ?
                  <DragElement
                    type="search"
                    list={this.state.config.search}
                    handleList={this.handleList}
                    handleMenu={this.handleSearch}
                    deleteMenu={this.deleteElement}
                    placeholder={this.state.dict['header.form.search.placeholder']}
                  /> : null
                }
              </div>
              <div className="action-list">
                <Tooltip placement="bottomLeft" overlayClassName="middle" title="在左侧工具栏《按钮》中,选择对应类型的按钮拖至此处添加,如选择按钮类型为表单、新标签页等含有配置页面的按钮,可在左侧工具栏-按钮-可配置按钮处,点击按钮完成相关配置。注:当设置按钮显示位置为表格时,显示列会增加操作列。">
                  <Icon type="question-circle" />
                </Tooltip>
                {!this.state.actionloading ?
                  <DragElement
                    type="action"
                    list={this.state.config.action}
                    handleList={this.handleList}
                    handleMenu={this.handleAction}
                    copyElement={(val) => this.handleAction(val, 'copy')}
                    deleteMenu={this.deleteElement}
                    placeholder={this.state.dict['header.form.action.placeholder']}
                  /> : null
                }
              </div>
              <div className="column-list">
                <Tooltip placement="bottomLeft" overlayClassName="middle" title="在左侧工具栏《显示列》中,选择对应类型的显示列拖至此处添加;或点击《添加显示列》按钮批量添加,选择批量添加时,需提前选择使用表。注:添加合并列时,需设置可选列。">
                  <Icon type="question-circle" />
                </Tooltip>
                <Switch checkedChildren="开" unCheckedChildren="关" defaultChecked={this.state.showColumnName} onChange={this.onColumnNameChange} />
                {!this.state.columnsloading ?
                  <DragElement
                    type="columns"
                    list={this.state.config.columns}
                    setting={this.state.config.setting}
                    gridBtn={this.state.config.gridBtn}
                    handleList={this.handleList}
                    handleMenu={this.handleColumn}
                    deleteMenu={this.deleteElement}
                    handleGridBtn={this.handleGridBtn}
                    showfield={this.state.showColumnName}
                    placeholder={this.state.dict['header.form.column.placeholder']}
                  /> : null
                }
              </div>
              <div className="tab-list">
                <Tooltip placement="bottomLeft" overlayClassName="middle" title="在左侧工具栏《标签页》中,选择对应类型的标签页拖至此处添加。">
                  <Icon type="question-circle" />
                </Tooltip>
                {/* {this.state.config.tabs.length > 0 ? <Icon type="setting" onClick={this.changeSetting} /> : null} */}
                {!this.state.tabloading ?
                  <TabDragElement
                    type="tabs"
                    list={this.state.config.tabs}
                    setting={this.state.config.setting}
                    handleList={this.handleList}
                    handleMenu={this.handleTab}
                    deleteMenu={this.deleteElement}
                    placeholder={this.state.dict['header.form.tab.placeholder']}
                  /> : null
                }
              </div>
            </Card>
          </div>
        </DndProvider>
        {/* 编辑搜索条件、按钮、显示列 */}
        <Modal
          title={this.state.modalTitle}
          visible={this.state.visible}
          width={700}
          onCancel={() => { this.setState({ visible: false }) }}
          footer={[
            this.state.formtemp === 'action' ?
            <Button key="delete" className="mk-btn mk-purple" onClick={this.creatFunc} loading={this.state.funcLoading}>{this.state.dict['header.menu.func.create']}</Button> : null,
            <Button key="cancel" onClick={() => { this.setState({ visible: false }) }}>{this.state.dict['header.cancel']}</Button>,
            <Button key="confirm" type="primary" onClick={this.handleSubmit}>{this.state.dict['header.confirm']}</Button>
          ]}
          destroyOnClose
        >
          {this.state.formtemp === 'search' ?
            <SearchForm
              dict={this.state.dict}
              formlist={this.state.formlist}
              card={this.state.card}
              wrappedComponentRef={(inst) => this.formRef = inst}
            /> : null
          }
          {this.state.formtemp === 'action' ?
            <ActionForm
              dict={this.state.dict}
              card={this.state.card}
              formlist={this.state.formlist}
              wrappedComponentRef={(inst) => this.formRef = inst}
            /> : null
          }
          {this.state.formtemp === 'columns' && this.state.card.type !== 'colspan' ?
            <ColumnForm
              dict={this.state.dict}
              card={this.state.card}
              formlist={this.state.formlist}
              wrappedComponentRef={(inst) => this.formRef = inst}
            /> : null
          }
          {this.state.formtemp === 'columns' && this.state.card.type === 'colspan' ?
            <ColspanForm
              dict={this.state.dict}
              card={this.state.card}
              columns={this.state.config.columns}
              wrappedComponentRef={(inst) => this.formRef = inst}
            /> : null
          }
          {this.state.formtemp === 'gridbtn' ?
            <GridBtnForm
              dict={this.state.dict}
              card={this.state.config.gridBtn}
              wrappedComponentRef={(inst) => this.formRef = inst}
            /> : null
          }
          {this.state.formtemp === 'tabs' ?
            <TabForm
              type="tabs"
              dict={this.state.dict}
              card={this.state.card}
              formlist={this.state.formlist}
              wrappedComponentRef={(inst) => this.formRef = inst}
            /> : null
          }
        </Modal>
        {/* 根据字段名添加显示列及搜索条件 */}
        <Modal
          wrapClassName="common-table-fields-modal"
          title={this.state.dict['header.edit']}
          visible={this.state.tableVisible}
          width={'65vw'}
          style={{minWidth: '900px', maxWidth: '1200px'}}
          cancelText={this.state.dict['header.close']}
          onOk={this.addFieldSubmit}
          onCancel={() => { // 取消添加
            this.setState({
              tableVisible: false,
              addType: ''
            })
          }}
          destroyOnClose
        >
          {this.state.addType && this.state.fields.length > 0 ?
            <EditCard data={this.state.fields} ref="searchcard" type={this.state.addType} dict={this.state.dict} /> : null
          }
          {(!this.state.fields || this.state.fields.length === 0) &&
            <Empty />
          }
        </Modal>
        {/* 设置全局配置及列表数据源 */}
        <Modal
          title={this.state.dict['header.edit']}
          visible={this.state.settingVisible}
          width={700}
          // onOk={this.settingSave}
          onCancel={() => { // 取消修改
            this.setState({
              settingVisible: false
            })
          }}
          footer={[
            <Button key="delete" className="mk-btn mk-purple" onClick={this.tableCreatFunc} loading={this.state.funcLoading}>{this.state.dict['header.menu.func.create']}</Button>,
            <Button key="cancel" onClick={() => { this.setState({ settingVisible: false }) }}>{this.state.dict['header.cancel']}</Button>,
            <Button key="confirm" type="primary" onClick={this.settingSave}>{this.state.dict['header.confirm']}</Button>
          ]}
          destroyOnClose
        >
          <SettingForm
            dict={this.state.dict}
            menu={this.props.menu}
            data={this.state.config.setting}
            columns={this.state.config.columns}
            usefulFields={this.props.permFuncField}
            wrappedComponentRef={(inst) => this.settingRef = inst}
          />
        </Modal>
        <Modal
          bodyStyle={{textAlign: 'center', color: '#000000', fontSize: '16px'}}
          closable={false}
          visible={this.state.closeVisible}
          onCancel={() => { this.setState({closeVisible: false}) }}
          footer={[
            <Button key="save" className="mk-btn mk-green" loading={this.state.menucloseloading} onClick={this.submitConfig}>{this.state.dict['header.save']}</Button>,
            <Button key="confirm" className="mk-btn mk-yellow" onClick={() => {this.props.handleConfig('')}}>{this.state.dict['header.notsave']}</Button>,
            <Button key="cancel" onClick={() => { this.setState({closeVisible: false}) }}>{this.state.dict['header.cancel']}</Button>
          ]}
          destroyOnClose
        >
          {this.state.dict['header.menu.config.placeholder']}
        </Modal>
        {this.state.loading && <Spin size="large" />}
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    permFuncField: state.permFuncField
  }
}
const mapDispatchToProps = () => {
  return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComTableConfig)
src/templates/subtableconfig/index.scss
New file
@@ -0,0 +1,478 @@
.common-table-board {
  position: fixed;
  z-index: 1070;
  padding-top: 48px;
  top: 0px;
  left: 0px;
  right: 0px;
  bottom: 0px;
  background: rgba(0, 0, 0, 0.35);
  display: flex;
  .tools {
    flex: 1;
    background: #ffffff;
    border-right: 1px solid #d9d9d9;
    height: 100%;
    overflow-y: hidden;
    padding-bottom: 30px;
    .ant-collapse-item {
      position: relative;
      border: 0;
    }
    .ant-input-search {
      margin-top: 10px;
    }
    .ant-collapse-item.ant-collapse-item-active {
      border-bottom: 1px solid #d9d9d9;
    }
    .ant-collapse-header {
      padding: 11px 16px 10px 40px;
      border-bottom: 1px solid #d9d9d9;
      background: #1890ff;
      color: #ffffff;
    }
    .ant-collapse-content-box {
      .ant-form-item {
        margin-bottom: 10px;
        .ant-form-item-label {
          text-align: left;
          height: 25px;
          line-height: 25px;
        }
      }
    }
    .search-element {
      padding-top: 10px;
      li {
        padding: 0px 16px 10px;
        div {
          cursor: move;
        }
      }
    }
    .config-btn-title {
      margin-top: 20px;
      margin-bottom: 10px;
      color: #1890ff;
      border-bottom: 1px solid #e8e8e8;
    }
    .tables {
      .ant-select-selection-selected-value {
        opacity: 0.4!important;
      }
    }
    .ant-list {
      margin-top: 20px;
      .ant-list-item {
        display: -webkit-box;
        padding-right: 20px;
        position: relative;
        padding-left: 5px;
        overflow: hidden;
        text-overflow: ellipsis;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        min-height: 55px;
        width: 100%;
        .anticon {
          position: absolute;
          top: 0px;
          right: 0px;
          padding: 3px 3px 10px 10px;
          cursor: pointer;
        }
        .bottom-mask {
          position: absolute;
          width: 100%;
          height: 8px;
          bottom: 0;
          left: 0;
          background: #ffffff;
          border-radius: 8px;
        }
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
  }
  .tools:hover {
    overflow-y: auto;
  }
  .tools::-webkit-scrollbar {
    width: 7px;
  }
  .tools::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0);
    background: rgba(0, 0, 0, 0);
  }
  .tools::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0);
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0);
    background: rgba(0, 0, 0, 0);
  }
  .tools:hover::-webkit-scrollbar-thumb {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .tools:hover::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
    border: 1px solid rgba(0, 0, 0, 0.07);
  }
  .setting {
    position: relative;
    width: calc(100vw - 235px);
    height: 100%;
    overflow-y: hidden;
    background: #ffffff;
    .ant-switch.big {
      min-width: 60px;
      height: 28px;
      line-height: 28px;
      margin-top: -2px;
      .ant-switch-inner {
        font-size: 14px;
      }
    }
    .ant-switch.big::after {
      width: 24px;
      height: 24px;
    }
    .ant-card-head {
      min-height: 44px;
    }
    .ant-card-head-title {
      padding: 5px 0;
      color: #1890ff;
    }
    .ant-card-extra {
      padding: 5px 0;
      button {
        margin-left: 20px;
      }
    }
    .ant-card-body {
      position: relative;
      padding: 0;
      .search-list {
        padding: 1px 24px 20px;
        min-height: 87px;
        border-bottom: 1px solid #d9d9d9;
        > .ant-row {
          min-height: 65px;
        }
        .ant-row .ant-col-6 {
          padding: 0 12px!important;
        }
        .ant-row.ant-form-item .ant-col {
          padding: 0;
        }
        .page-card {
          position: relative;
          background: #ffffff;
          border-radius: 2px;
          padding-top: 15px;
          .ant-form-item {
            cursor: move;
            display: flex;
            margin-bottom: 0px;
            .ant-form-item-label {
              width: 100px;
              height: 40px;
              label {
                width: 100%;
                cursor: move;
                overflow: hidden;
                display: inline-block;
                text-overflow: ellipsis;
                white-space: nowrap;
              }
            }
            .ant-form-item-control-wrapper {
              flex: 1 1;
              .ant-select {
                width: 100%;
                margin-top: 4px;
              }
              .ant-calendar-picker {
                margin-top: 4px;
              }
              .input-mask {
                position: absolute;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                opacity: 0;
                z-index: 2;
              }
              .data-range .ant-calendar-picker-input {
                padding: 4px 20px 4px 5px;
                font-size: 13px;
              }
            }
          }
          .edit {
            position: absolute;
            left: 0;
            top: 5px;
            color: #1890ff;
            cursor: pointer;
            display: none;
          }
          .edit.close {
            left: 20px;
            color: #ff4d4f;
          }
        }
        .page-card:hover {
          .edit {
            display: inline-block;
          }
        }
        .ant-calendar-picker {
          min-width: 100px!important;
          width: 100%;
        }
      }
      .action-list {
        padding: 0px 20px 15px;
        min-height: 82px;
        > .ant-row {
          min-height: 67px;
        }
        .page-card {
          display: inline-block;
          margin: 0px 0px 0px 0px;
          padding: 15px 10px 0 0;
          position: relative;
          div {
            cursor: move;
          }
          .edit {
            position: absolute;
            left: 0;
            top: 0px;
            color: #1890ff;
            cursor: pointer;
            display: none;
          }
          .edit.close {
            left: 40px;
            color: #ff4d4f;
          }
          .edit.copy {
            left: 20px;
            color: #26C281;
          }
          button {
            cursor: move;
            .anticon-table {
              font-size: 10px;
              position: absolute;
              right: 1px;
              bottom: 0px;
            }
          }
        }
        .page-card:hover {
          .edit {
            display: inline-block;
          }
        }
      }
      .column-list {
        position: relative;
        padding: 0px 20px;
        .ant-switch {
          position: absolute;
          right: 20px;
          top: -10px;
        }
        > .ant-row {
          background: #fafafa;
          border-radius: 4px;
          min-height: 47px;
          border: 1px solid #e8e8e8;
          .column-box {
            display: flex;
          }
          .column-box:not(:first-child) {
            border-top: 1px solid #e8e8e8;
          }
          .page-card {
            position: relative;
            padding: 0px;
            min-height: 45px;
            > div {
              padding: 12px 0px 0px;
              cursor: move;
              height: 100%;
              .ant-table-column-sorters {
                padding: 0px 8px 12px;
              }
              .ant-table-column-fields {
                padding: 0px 8px 5px;
              }
            }
            .ant-table-column-sorter {
              position: relative;
              display: inline-block;
              width: 24px;
              font-size: 12px;
              color: #bfbfbf;
              .anticon-caret-up {
                position: relative;
                left: 10px;
                top: -3px;
              }
              .anticon-caret-down {
                position: relative;
                left: -2px;
                top: 3px;
              }
            }
            .edit {
              position: absolute;
              left: 0;
              top: 0px;
              color: #1890ff;
              cursor: pointer;
              display: none;
            }
            .edit.close {
              left: 20px;
              color: #ff4d4f;
            }
            .ant-checkbox-inner {
              margin-top: 14px;
              margin-left: calc(50% - 8px);
            }
          }
          .page-card:hover {
            .edit {
              display: inline-block;
            }
          }
          .page-card:not(:last-child) {
            border-right: 1px solid #e8e8e8;
          }
        }
      }
      .tab-list {
        position: relative;
        padding: 30px 20px 0px;
        .ant-switch {
          position: absolute;
          right: 20px;
          top: 20px;
        }
        > .ant-row {
          min-height: 47px;
          .page-card {
            position: relative;
            padding: 0px;
            > div {
              padding: 12px 0px 0px;
              cursor: move;
            }
          }
          .ant-tabs-tab {
            .edit {
              position: absolute;
              left: 0;
              top: 0px;
              color: #1890ff;
              cursor: pointer;
              display: none;
            }
            .edit.close {
              left: 20px;
              color: #ff4d4f;
            }
          }
          .ant-tabs-bar {
            min-height: 55px;
          }
          .ant-tabs-tab:hover {
            .edit {
              display: inline-block;
            }
          }
        }
        > .anticon-setting {
          position: absolute;
          font-size: 18px;
          right: 15px;
          top: 30px;
        }
      }
      > .anticon-setting {
        position: absolute;
        font-size: 18px;
        right: 15px;
        top: 10px;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      position: relative;
      left: -15px;
      top: 5px;
    }
  }
  .setting:hover {
    overflow-y: auto;
  }
  .setting::-webkit-scrollbar {
    width: 7px;
  }
  .setting::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .setting::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.07);
    background: rgba(0, 0, 0, 0);
  }
  .ant-spin {
    position: absolute;
    margin-left: calc(50vw - 22px);
    margin-top: 30vh;
  }
}
.common-table-fields-modal {
  .ant-modal {
    top: 50px;
    padding-bottom: 5px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
      overflow-y: auto;
      .ant-empty {
        margin: 15vh 8px;
      }
    }
    .ant-modal-body::-webkit-scrollbar {
      width: 7px;
    }
    .ant-modal-body::-webkit-scrollbar-thumb {
      border-radius: 5px;
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
      background: rgba(0, 0, 0, 0.13);
    }
    .ant-modal-body::-webkit-scrollbar-track {
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
      border-radius: 3px;
      border: 1px solid rgba(0, 0, 0, 0.07);
      background: rgba(0, 0, 0, 0);
    }
  }
}
src/templates/subtableconfig/menuform/index.jsx
New file
@@ -0,0 +1,97 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select } from 'antd'
import './index.scss'
class MainSearch extends Component {
  static propTpyes = {
    dict: PropTypes.object, // 字典项
    formlist: PropTypes.array
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.props.formlist.forEach((item, index) => {
      if (item.type === 'text') { // 文本搜索
        fields.push(
          <Col span={24} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={24} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  getPopupContainer={() => document.getElementById('qazxcvbn')}
                >
                  {item.options.map(option =>
                    <Select.Option id={option.MenuID} title={option.text} key={option.MenuID} value={option.MenuID}>
                      {option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 24 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 24 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form" id="qazxcvbn">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/templates/subtableconfig/menuform/index.scss
src/templates/subtableconfig/searchform/index.jsx
New file
@@ -0,0 +1,341 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, Icon, Radio, notification } from 'antd'
import { dateOptions, matchReg } from '@/utils/option.js'
import EditTable from '../editable'
import './index.scss'
const { TextArea } = Input
class MainSearch extends Component {
  static propTpyes = {
    dict: PropTypes.object,  // 字典项
    formlist: PropTypes.any, // 表单
    card: PropTypes.object   // 搜索条件信息
  }
  state = {
    openType: null,          // 搜索条件显示类型
    resourceType: null,      // 下拉搜索时,选项来源类型
    formlist: null           // 表单
  }
  /**
   * @description 表单预处理
   * 1、根据表单类型,显示表单可编辑项
   * 2、下拉选择,根据数据源类型显示相关配置
   */
  UNSAFE_componentWillMount () {
    const { formlist } = this.props
    let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    let resourceType = formlist.filter(cell => cell.key === 'resourceType')[0].initVal
    let _options = ['label', 'field', 'initval', 'type', 'match']                // 默认显示项
    if ((type === 'select' || type === 'link') && resourceType === '0') {        // 下拉选择类型、选项为自定义资源
      _options = [..._options, 'resourceType', 'setAll', 'options', 'display']
    } else if ((type === 'select' || type === 'link') && resourceType === '1') { // 下拉选择类型、选项为后台数据源中获取
      _options = [..._options, 'resourceType', 'setAll', 'dataSource', 'valueField', 'valueText', 'orderBy', 'orderType', 'display']
    }
    if (type === 'link') { // 关联类型、增加关联上级的字段名
      _options = [..._options, 'linkField']
    }
    this.setState({
      openType: type,
      resourceType: resourceType,
      formlist: formlist.map(form => {
        // 表单为初始值字段,且数据类型属于时间类型时,设置初始值为下拉选择,并重置选择项
        if (form.key === 'initval' && dateOptions.hasOwnProperty(type)) {
          form.options = dateOptions[type]
          form.type = 'select'
        }
        // 表单为匹配字段时,根据不同的类型,显示对应的匹配规则
        if (form.key === 'match') {
          if (type === 'text') {
            form.options = matchReg.text
          } else if (type === 'select' || type === 'link') {
            form.options = matchReg.select
          } else if (type === 'date') {
            form.options = matchReg.date
          } else if (type === 'datemonth') {
            form.options = matchReg.datemonth
          } else if (type === 'dateweek' || type === 'daterange') {
            form.options = matchReg.daterange
          }
        }
        form.hidden = !_options.includes(form.key)
        return form
      })
    })
  }
  /**
   * @description 搜索条件类型切换
   */
  openTypeChange = (key, value) => {
    const { resourceType } = this.state
    if (key === 'type') {
      let _options = ['label', 'field', 'initval', 'type', 'match']
      if ((value === 'select' || value === 'link') && resourceType === '0') {        // 下拉选择类型、选项为自定义资源
        _options = [..._options, 'resourceType', 'setAll', 'options', 'display']
      } else if ((value === 'select' || value === 'link') && resourceType === '1') { // 下拉选择类型、选项为后台数据源中获取
        _options = [..._options, 'resourceType', 'setAll', 'dataSource', 'valueField', 'valueText', 'orderBy', 'orderType', 'display']
      }
      if (value === 'link') {
        _options = [..._options, 'linkField']
      }
      this.setState({
        openType: value,
        formlist: this.state.formlist.map(form => {
          form.hidden = !_options.includes(form.key)            // 隐藏表单
          if (form.key === 'initval') {
            if (dateOptions.hasOwnProperty(value)) { // 根据搜索条件类型,选择初始值的类型及数据
              form.options = dateOptions[value]
              form.type = 'select'
            } else {
              form.type = 'text'
            }
            form.initVal = ''                                    // 搜索条件类型切换时,初始值置空
            form.hidden = true
          } else if (form.key === 'match') {                     // 搜索条件类型切换时,匹配规则类型对应切换
            if (value === 'text') {
              form.options = matchReg.text
            } else if (value === 'select' || value === 'link') {
              form.options = matchReg.select
            } else if (value === 'date') {
              form.options = matchReg.date
            } else if (value === 'datemonth') {
              form.options = matchReg.datemonth
            } else if (value === 'dateweek' || value === 'daterange') {
              form.options = matchReg.daterange
            }
            form.hidden = true
          }
          return form
        })
      }, () => {
        this.setState({
          formlist: this.state.formlist.map(form => {
            if (form.key === 'initval') {
              form.hidden = false
            } else if (form.key === 'match') {
              form.initVal = form.options[0].value
              form.hidden = false
            }
            return form
          })
        })
      })
    }
  }
  /**
   * @description 数据源类型切换
   */
  onChange = (e, key) => {
    const { openType } = this.state
    let value = e.target.value
    if (key === 'resourceType') {
      let _options = ['label', 'field', 'initval', 'type', 'match', 'resourceType', 'setAll', 'display']
      if (value === '0') {
        _options = [..._options, 'options']
      } else if (value === '1') {
        _options = [..._options, 'dataSource', 'valueField', 'valueText', 'orderBy', 'orderType']
      }
      if (openType === 'link') {
        _options = [..._options, 'linkField']
      }
      this.setState({
        resourceType: value,
        formlist: this.state.formlist.map(form => {
          form.hidden = !_options.includes(form.key)
          return form
        })
      })
    }
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.state.formlist.forEach((item, index) => {
      if (item.hidden) return
      if (item.type === 'text') { // 文本搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children[2].toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.openTypeChange(item.key, value)}}
                  getPopupContainer={() => document.getElementById('commontable-search-form-box')}
                >
                  {item.options.map(option =>
                    <Select.Option id={option.value} title={option.text} key={option.value} value={option.value}>
                      {item.key === 'icon' && <Icon type={option.text} />} {option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onChange(e, item.key)}}>
                  {
                    item.options.map(option => {
                      return (
                        <Radio key={option.value} value={option.value}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>,
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'textarea') {
        fields.push(
          <Col span={20} offset={4} key={index}>
            <Form.Item className="text-area">
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<TextArea rows={4} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'options') {
        fields.push(
          <Col span={20} offset={4} key={index}>
            <EditTable data={item.initVal} type={this.state.openType} ref="editTable"/>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          let isvalid = true
          values.id = this.props.card.id
          values.uuid = this.props.card.uuid
          // 下拉菜单或关联菜单
          if ((values.type === 'select' || values.type === 'link') && values.resourceType === '0') {
            values.options = this.refs.editTable.state.dataSource
            values.dataSource = ''
            let emptys = []
            if (values.type === 'select') {
              emptys = values.options.filter(op => !(op.Value && op.Text))
            } else {
              emptys = values.options.filter(op => !(op.Value && op.Text && op.ParentID))
            }
            if (emptys.length > 0) {
              isvalid = false
            }
          } else if ((values.type === 'select' || values.type === 'link') && values.resourceType === '1') {
            values.options = []
          }
          if (isvalid) {
            resolve({
              type: 'search',
              values
            })
          } else {
            notification.warning({
              top: 92,
              message: this.props.dict['header.form.selectItem.error'],
              duration: 10
            })
          }
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-search-form" id="commontable-search-form-box">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainSearch)
src/templates/subtableconfig/searchform/index.scss
New file
@@ -0,0 +1,13 @@
.ant-advanced-search-form.commontable-search-form {
  min-height: 180px;
  .ant-col-offset-4 {
    padding-left: 6px!important;
    padding-bottom: 20px;
  }
  .ant-form-item.text-area {
    margin-bottom: 0px;
    .ant-form-item-control-wrapper {
      width: 100%;
    }
  }
}
src/templates/subtableconfig/settingform/index.jsx
New file
@@ -0,0 +1,237 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Select, Tooltip, Icon, notification } from 'antd'
import './index.scss'
const { TextArea } = Input
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object, // 字典项
    menu: PropTypes.object,
    data: PropTypes.object,
    columns: PropTypes.array,
    usefulFields: PropTypes.array
  }
  state = {
    interType: this.props.data.interType || 'inner',
    columns: this.props.columns.filter(item => item.field && item.type !== 'colspan')
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          values.actionfixed = values.actionfixed === 'true'
          values.columnfixed = values.columnfixed === 'true'
          if (values.interType === 'inner' && !values.innerFunc && !values.dataresource) {
            notification.warning({
              top: 92,
              message: '请自定义函数或填写数据源!',
              duration: 10
            })
          } else {
            resolve(values)
          }
        } else {
          reject(err)
        }
      })
    })
  }
  onChange = (e) => {
    this.setState({
      interType: e.target.value
    })
  }
  render() {
    const { data, dict, menu, usefulFields } = this.props
    const { getFieldDecorator } = this.props.form
    const { interType, columns } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    let primaryKey = data.primaryKey
    if (primaryKey) {
      let field = columns.filter(column => column.field === primaryKey)
      if (field.length !== 1) {
        primaryKey = ''
      }
    }
    let str = '^(' + usefulFields.join('|') + ')'
    let _patten = new RegExp(str + '[0-9a-zA-Z_]*$', 'g')
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-setting-form" id="commontable-setting-form">
        <Row gutter={24}>
          <Col span={12}>
            <Form.Item label="表名">
              {getFieldDecorator('tableName', {
                initialValue: data.tableName,
                rules: [
                  {
                    required: true,
                    message: dict['form.required.input'] + '表名!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label="表格属性">
              {getFieldDecorator('tableType', {
                initialValue: data.tableType
              })(
                <Select
                  getPopupContainer={() => document.getElementById('commontable-setting-form')}
                >
                  <Select.Option value="">不可选</Select.Option>
                  <Select.Option value="radio">单选</Select.Option>
                  <Select.Option value="checkbox">多选</Select.Option>
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label={dict['header.form.intertype']}>
              {getFieldDecorator('interType', {
                initialValue: data.interType || 'inner'
              })(
                <Radio.Group onChange={this.onChange}>
                  <Radio value="inner">{dict['header.form.interface.inner']}</Radio>
                  <Radio value="outer">{dict['header.form.interface.outer']}</Radio>
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
          {interType === 'outer' ? <Col span={12}>
            <Form.Item label={dict['header.form.interface']}>
              {getFieldDecorator('interface', {
                initialValue: data.interface || '',
                rules: [
                  {
                    required: true,
                    message: dict['form.required.input'] + dict['header.form.interface'] + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col> : null}
          {interType === 'outer' ? <Col span={12}>
            <Form.Item label={dict['header.form.outerFunc']}>
              {getFieldDecorator('outerFunc', {
                initialValue: data.outerFunc || ''
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col> : null}
          {interType !== 'outer' ? <Col span={12}>
            <Form.Item label={
              <Tooltip placement="topLeft" overlayClassName="middle" title={`可自定义数据处理函数,函数名称需以${usefulFields.join(', ')}等字符开始;未设置时会调用系统函数,使用系统函数需完善数据源。`}>
                <Icon type="question-circle" />
                {dict['header.form.innerFunc']}
              </Tooltip>
            }>
              {getFieldDecorator('innerFunc', {
                initialValue: data.innerFunc || '',
                rules: [
                  {
                    pattern: _patten,
                    message: '名称只允许包含数字、字母和下划线,且以指定字符开始。'
                  }, {
                    max: 50,
                    message: '内部函数名称不超过50个字符。'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col> : null}
          {interType !== 'outer' ? <Col span={24}>
            <Form.Item help={'数据ID:' + menu.MenuID} label={
              <Tooltip placement="topLeft" title="使用系统函数时,需填写数据源,自定义函数时,可忽略。">
                <Icon type="question-circle" />
                {'数据源'}
              </Tooltip>
            } className="textarea">
              {getFieldDecorator('dataresource', {
                initialValue: data.dataresource
              })(<TextArea rows={4} />)}
            </Form.Item>
          </Col> : null}
          <Col span={12}>
            <Form.Item label="固定按钮">
              {getFieldDecorator('actionfixed', {
                initialValue: data.actionfixed ? 'true' : 'false'
              })(
                <Select>
                  <Select.Option value="true">是</Select.Option>
                  <Select.Option value="false">否</Select.Option>
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label="固定列">
              {getFieldDecorator('columnfixed', {
                initialValue: data.columnfixed ? 'true' : 'false'
              })(
                <Select>
                  <Select.Option value="true">是</Select.Option>
                  <Select.Option value="false">否</Select.Option>
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label="主键">
              {getFieldDecorator('primaryKey', {
                initialValue: primaryKey ? primaryKey : (columns.length === 0 ? 'ID' : '')
              })(
                <Select
                  getPopupContainer={() => document.getElementById('commontable-setting-form')}
                >
                  <Select.Option key='unset' value="">不设置</Select.Option>
                  {columns.length === 0 ?
                    <Select.Option key='id' value="ID">ID</Select.Option> : null
                  }
                  {columns.map((option, index) =>
                    <Select.Option id={option.uuid} title={option.label} key={index} value={option.field}>{option.label}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label="默认排序">
              {getFieldDecorator('order', {
                initialValue: data.order,
                rules: [
                  {
                    required: true,
                    message: dict['form.required.input'] + '默认排序字段!'
                  }
                ]
              })(<Input placeholder="ID asc, UID desc" autoComplete="off" />)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(SettingForm)
src/templates/subtableconfig/settingform/index.scss
New file
@@ -0,0 +1,14 @@
.ant-advanced-search-form.commontable-setting-form {
  .textarea {
    .ant-form-item-label {
      width: 16.3%;
    }
    .ant-form-item-control-wrapper {
      width: 83.33333333%;
    }
  }
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
}
src/templates/subtableconfig/source.jsx
New file
@@ -0,0 +1,362 @@
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/comtable.js'
import enUS from '@/locales/en-US/comtable.js'
const CommonDict = (!localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN') ? zhCN : enUS
class CommonTableBaseData {
  baseConfig = {
    type: 'system',
    enabled: false,
    setting: {
      actionfixed: false,
      columnfixed: false,
      tableName: '',
      tableType: 'checkbox',
      primaryKey: '',
      order: '',
      dataresource: '',
      interType: 'inner',
      innerFunc: '',
      interface: '',
      outerFunc: ''
    },
    tables: [],
    search: [
      {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'label',
        field: '',
        initval: '',
        type: 'text',
        resourceType: '0',
        setAll: 'false',
        options: [],
        dataSource: '',
        linkField: '',
        valueField: '',
        valueText: '',
        orderBy: '',
        orderType: 'asc',
        match: 'like',
        display: 'dropdown'
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'label',
        field: '',
        initval: '',
        type: 'select',
        resourceType: '0',
        setAll: 'false',
        options: [],
        dataSource: '',
        linkField: '',
        valueField: '',
        valueText: '',
        orderBy: '',
        orderType: 'asc',
        match: 'equal',
        display: 'dropdown'
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'label',
        field: '',
        initval: '',
        type: 'date',
        resourceType: '0',
        setAll: 'false',
        options: [],
        dataSource: '',
        linkField: '',
        valueField: '',
        valueText: '',
        orderBy: '',
        orderType: 'asc',
        match: 'greater',
        display: 'dropdown'
      }
    ],
    action: [
      {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'add',
        intertype: 'inner',
        innerFunc: '',
        interface: '',
        method: 'POST',
        outerFunc: '',
        sql: '',
        sqlType: '',
        callbackFunc: '',
        Ot: 'notRequired',
        position: 'toolbar',
        execSuccess: 'grid',
        execError: 'never',
        OpenType: 'pop',
        pageTemplate: '',
        url: '',
        icon: 'plus',
        class: 'green'
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'update',
        intertype: 'inner',
        innerFunc: '',
        interface: '',
        method: 'POST',
        outerFunc: '',
        sql: '',
        sqlType: '',
        callbackFunc: '',
        Ot: 'requiredSgl',
        position: 'grid',
        execSuccess: 'grid',
        execError: 'never',
        OpenType: 'pop',
        pageTemplate: '',
        url: '',
        icon: 'form',
        class: 'purple'
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'delete',
        intertype: 'inner',
        innerFunc: '',
        interface: '',
        method: 'POST',
        outerFunc: '',
        sql: '',
        sqlType: '',
        callbackFunc: '',
        Ot: 'required',
        position: 'toolbar',
        execSuccess: 'grid',
        execError: 'never',
        OpenType: 'prompt',
        pageTemplate: '',
        url: '',
        icon: 'delete',
        class: 'red'
      }
    ],
    columns: [
      {
        origin: true,
        uuid: Utils.getuuid(),
        Align: 'left',
        label: 'label',
        field: '',
        Hide: 'false',
        IsSort: 'true',
        type: 'text',
        Width: 120
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        Align: 'left',
        label: 'label',
        field: '',
        Hide: 'false',
        IsSort: 'true',
        type: 'text',
        Width: 120
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        Align: 'left',
        label: 'label',
        field: '',
        Hide: 'false',
        IsSort: 'true',
        type: 'text',
        Width: 120
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        Align: 'left',
        label: 'label',
        field: '',
        Hide: 'false',
        IsSort: 'true',
        type: 'text',
        Width: 120
      }
    ],
    gridBtn: {
      display: false,
      Align: 'center',
      IsSort: 'false',
      uuid: Utils.getuuid(),
      label: CommonDict['header.form.column.action'],
      type: 'action',
      style: 'button',
      show: 'horizontal',
      Width: 120
    },
    tabs: [
      {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'tab1',
        icon: '',
        type: 'SubTable',
        linkTab: '',
        supMenu: ''
      },
      {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'tab2',
        icon: '',
        type: 'SubTable',
        linkTab: '',
        supMenu: ''
      }
    ]
  }
  searchItems = [
    {
      type: 'search',
      label: '文本框',
      subType: 'text',
      url: ''
    },
    {
      type: 'search',
      label: '下拉框',
      subType: 'select',
      url: ''
    },
    {
      type: 'search',
      label: '时间框(天)',
      subType: 'date',
      url: ''
    },
    {
      type: 'search',
      label: '时间框(周)',
      subType: 'dateweek',
      url: ''
    },
    {
      type: 'search',
      label: '时间框(月)',
      subType: 'datemonth',
      url: ''
    },
    {
      type: 'search',
      label: '时间框(区间)',
      subType: 'daterange',
      url: ''
    }
  ]
  actionItems = [
    {
      type: 'action',
      label: CommonDict['header.form.popform'],
      subType: 'pop',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.popview'],
      subType: 'popview',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.prompt'],
      subType: 'prompt',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.exec'],
      subType: 'exec',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.tab'],
      subType: 'tab',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.excelIn'],
      subType: 'excelIn',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.excelOut'],
      subType: 'excelOut',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.blank'],
      subType: 'blank',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.newpage.inner'],
      subType: 'innerpage',
      url: ''
    },
    {
      type: 'action',
      label: CommonDict['header.form.newpage.outer'],
      subType: 'outerpage',
      url: ''
    }
  ]
  columnItems = [
    {
      type: 'columns',
      label: CommonDict['header.form.text'],
      subType: 'text',
      url: ''
    },
    {
      type: 'columns',
      label: CommonDict['header.form.picture'],
      subType: 'picture',
      url: ''
    },
    {
      type: 'columns',
      label: CommonDict['header.form.number'],
      subType: 'number',
      url: ''
    },
    {
      type: 'columns',
      label: CommonDict['header.form.colspan'],
      subType: 'colspan',
      url: ''
    }
  ]
  tabItems = [
    {
      type: 'tabs',
      label: CommonDict['header.menu.tab.subtable'],
      subType: 'SubTable',
    }
  ]
}
export default new CommonTableBaseData()
src/templates/subtableconfig/tabdragelement/card.jsx
New file
@@ -0,0 +1,41 @@
import React from 'react'
import { useDrag, useDrop } from 'react-dnd'
import { Icon } from 'antd'
import './index.scss'
const Card = ({ id, type, card, moveCard, findCard, editCard, delCard, hasDrop }) => {
  const originalIndex = findCard(id).index
  const [{ isDragging }, drag] = useDrag({
    item: { type: type, id, originalIndex },
    collect: monitor => ({
      isDragging: monitor.isDragging(),
    }),
  })
  const [, drop] = useDrop({
    accept: type,
    canDrop: () => true,
    drop: (item) => {
      if (!item.hasOwnProperty('originalIndex')) {
        hasDrop(card)
      }
    },
    hover({ id: draggedId }) {
      if (!draggedId) return
      if (draggedId !== id) {
        const { index: overIndex } = findCard(id)
        moveCard(draggedId, overIndex)
      }
    },
  })
  const opacity = isDragging ? 0 : 1
  return (
    <div className="page-card" style={{ opacity: opacity}}>
      <div ref={node => drag(drop(node))}>
        {card.icon ? <Icon type={card.icon} /> : null}
        {card.label}
      </div>
    </div>
  )
}
export default Card
src/templates/subtableconfig/tabdragelement/index.jsx
New file
@@ -0,0 +1,109 @@
import React, { useState } from 'react'
import { useDrop } from 'react-dnd'
import update from 'immutability-helper'
import { Tabs, Icon } from 'antd'
import Utils from '@/utils/utils.js'
import Card from './card'
import './index.scss'
const { TabPane } = Tabs
const Container = ({list, type, setting, placeholder, handleList, handleMenu, deleteMenu }) => {
  let target = null
  const [cards, setCards] = useState(list)
  const moveCard = (id, atIndex) => {
    const { card, index } = findCard(id)
    const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
    setCards(_cards)
    handleList(type, _cards)
  }
  const findCard = id => {
    const card = cards.filter(c => `${c.uuid}` === id)[0]
    return {
      card,
      index: cards.indexOf(card),
    }
  }
  const hasDrop = (item) => {
    target = item
  }
  const [, drop] = useDrop({
    accept: type,
    drop(item) {
      if (item.hasOwnProperty('originalIndex')) {
        return
      }
      let newcard = {}
      newcard.uuid = Utils.getuuid()
      newcard.label = 'tab'
      newcard.icon = ''
      newcard.type = item.subType
      newcard.linkId = ''
      newcard.description = ''
      let targetId = cards.length > 0 ? cards[cards.length - 1].uuid : 0
      if (target) {
        targetId = target.uuid
      }
      const { index: overIndex } = findCard(`${targetId}`)
      let targetIndex = overIndex
      if (!target) {
        targetIndex++
      }
      if (targetIndex < 0) {
        targetIndex = 0
      }
      const _cards = update(cards, { $splice: [[targetIndex, 0, newcard]] })
      setCards(_cards)
      handleList(type, _cards, newcard)
      target = null
    }
  })
  const edit = (card) => {
    handleMenu(card)
  }
  const del = (card) => {
    deleteMenu({card: card, type: type})
  }
  return (
    <div ref={drop} className="ant-row">
      <Tabs defaultActiveKey="0" tabPosition="top">
        {cards.map((card, index) => (
          <TabPane tab={
            <div key={card.uuid}>
              <Card
                key={card.uuid}
                id={`${card.uuid}`}
                type={type}
                card={card}
                moveCard={moveCard}
                findCard={findCard}
                hasDrop={hasDrop}
              />
              <Icon className="edit" type="edit" onClick={() => edit(card)} />
              <Icon className="edit close" type="close" onClick={() => del(card)} />
            </div>
          } key={`${index}`}>
            {card.description}
          </TabPane>
        ))}
      </Tabs>
      {cards.length === 0 ?
        <div className="commontab-drawarea-placeholder">
          {placeholder}
        </div> : null
      }
    </div>
  )
}
export default Container
src/templates/subtableconfig/tabdragelement/index.scss
New file
@@ -0,0 +1,15 @@
.common-source-item {
  display: block;
  box-shadow: 0px 0px 2px #bcbcbc;
  padding: 0.4rem 0.7rem;
  background-color: white;
  margin: 0px 0px 10px;
  cursor: move;
  border-radius: 4px;
}
.commontab-drawarea-placeholder {
  position: absolute;
  top: 25px;
  left: calc(50% - 50px);
  color: #bcbcbc;
}
src/templates/subtableconfig/tabform/index.jsx
New file
@@ -0,0 +1,149 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, Icon } from 'antd'
import './index.scss'
class MainTab extends Component {
  static propTpyes = {
    type: PropTypes.string,  // 类型
    dict: PropTypes.object,  // 字典项
    formlist: PropTypes.any, // 表单
    card: PropTypes.object   // 标签页信息
  }
  state = {
    formlist: null           // 表单
  }
  /**
   * @description 表单预处理
   */
  UNSAFE_componentWillMount () {
    const { formlist } = this.props
    let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    console.log(type)
    this.setState({
      formlist: formlist
    })
  }
  /**
   * @description 标签页类型切换
   */
  openTypeChange = (key, value) => {
    console.log(value)
    // if (key === 'type') {
    //   console.log(value)
    //   this.setState({
    //     formlist: this.state.formlist.map(form => {
    //       return form
    //     })
    //   }, () => {
    //     this.setState({
    //       formlist: this.state.formlist.map(form => {
    //         return form
    //       })
    //     })
    //   })
    // }
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.state.formlist.forEach((item, index) => {
      if (item.hidden) return
      if (item.type === 'text') { // 文本搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.openTypeChange(item.key, value)}}
                >
                  {item.options.map((option, i) =>
                    <Select.Option id={`${i}`} title={option.text} key={`${i}`} value={option.value}>
                      {item.key === 'icon' && i !== 0 ? <Icon type={option.text} /> : option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          values.uuid = this.props.card.uuid
          resolve({
            type: this.props.type,
            values
          })
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-tab-form">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainTab)
src/templates/subtableconfig/tabform/index.scss
New file
@@ -0,0 +1,3 @@
.ant-advanced-search-form.commontable-tab-form {
  min-height: 180px;
}
src/templates/subtableconfig/transferform/index.jsx
New file
@@ -0,0 +1,75 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Transfer } from 'antd'
import './index.scss'
class TransferForm extends Component {
  static propTypes = {
    columns: PropTypes.array,
    selected: PropTypes.array,
    dict: PropTypes.object, // 字典项
  }
  state = {
    data: [],
    targetKeys: [],
    selectedKeys: []
  }
  handleChange = (nextTargetKeys, direction, moveKeys) => {
    this.setState({ targetKeys: nextTargetKeys })
  }
  handleSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
    this.setState({ selectedKeys: [...sourceSelectedKeys, ...targetSelectedKeys] })
  }
  UNSAFE_componentWillMount() {
    let datas = new Map()
    this.props.columns.forEach(item => {
      if (item.field) {
        datas.set(item.uuid, item)
      }
    })
    let selecteds = []
    this.props.selected.forEach(item => {
      if (datas.has(item)) {
        selecteds.push(item)
        // datas.delete(item)
      }
    })
    this.setState({
      data: [...datas.values()].map(item => {
        return {
          key: item.uuid,
          title: item.label,
          description: ''
        }
      }),
      targetKeys: selecteds
    })
  }
  render() {
    const { targetKeys, selectedKeys } = this.state
    return (
      <div className="common-table-columns-transfer">
        <Transfer
          dataSource={this.state.data}
          titles={[this.props.dict['header.form.column.source'], this.props.dict['header.form.column.target']]}
          targetKeys={targetKeys}
          locale={{itemUnit: this.props.dict['header.form.column.itemUnit'], itemsUnit: this.props.dict['header.form.column.itemsUnit']}}
          selectedKeys={selectedKeys}
          onChange={this.handleChange}
          onSelectChange={this.handleSelectChange}
          render={item => item.title}
        />
      </div>
    )
  }
}
export default TransferForm
src/templates/subtableconfig/transferform/index.scss
New file
@@ -0,0 +1,6 @@
.common-table-columns-transfer {
  padding-left: 18px;
  .ant-transfer-list {
    width: 296px;
  }
}