king
2022-08-26 665b7b5866f726030ec0335834c2ba80060c86e8
2022-08-26
21个文件已修改
11个文件已添加
3658 ■■■■ 已修改文件
public/options.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/columns/editColumn/formconfig.jsx 424 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/columns/editColumn/index.jsx 398 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/columns/editColumn/index.scss 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/columns/index.jsx 618 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/columns/index.scss 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/index.jsx 554 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/index.scss 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/base-table/options.jsx 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modalconfig/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/tableshell/card.jsx 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/tableshell/index.jsx 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/tableshell/index.scss 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/modalconfig/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/mob/searchconfig/index.scss 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/index.scss 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/index.scss 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/index.jsx 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/index.scss 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tablecomponent/index.jsx 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tablecomponent/index.scss 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/index.scss 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/treepageconfig/index.scss 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/imdesign/index.scss 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.scss 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mobdesign/index.scss 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/pcdesign/index.scss 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/popdesign/index.scss 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tabledesign/index.jsx 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tabledesign/index.scss 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/tabledesign/menuform/index.jsx 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/options.json
@@ -8,7 +8,7 @@
  "filter": "false",
  "defaultApp": "mk",
  "defaultLang": "zh-CN",
  "WXAppID": "",
  "WXAppID": "wx4d8a34c8d4494872",
  "WXminiAppID": "",
  "debugger": false,
  "licenseKey": "",
src/menu/components/table/base-table/columns/editColumn/formconfig.jsx
New file
@@ -0,0 +1,424 @@
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
const Formdict = sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
/**
 * @description 获取显示列表单配置信息
 * @param {object} card       // 搜索条件对象
 */
export function getColumnForm (card, fields = []) {
  let appType = sessionStorage.getItem('appType')
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
    try {
      roleList = JSON.parse(roleList)
    } catch (e) {
      roleList = []
    }
  } else {
    roleList = []
  }
  let menulist = []
  if (appType === 'pc') {
    menulist = sessionStorage.getItem('appMenus')
  } else if (!appType) {
    menulist = sessionStorage.getItem('fstMenuList')
  }
  if (menulist) {
    try {
      menulist = JSON.parse(menulist)
    } catch (e) {
      menulist = []
    }
  } else {
    menulist = []
  }
  let options = [{
    value: 'text',
    text: Formdict['model.form.text']
  }, {
    value: 'number',
    text: Formdict['model.form.number']
  }, {
    value: 'picture',
    text: Formdict['model.form.picture']
  }, {
    value: 'link',
    text: Formdict['model.form.href']
  }, {
    value: 'textarea',
    text: Formdict['model.form.textarea']
  }, {
    value: 'custom',
    text: '自定义列'
  }, {
    value: 'colspan',
    text: '合并列'
  }, {
    value: 'formula',
    text: '公式'
  }, {
    value: 'index',
    text: '序号'
  }]
  if (!card.isSub) {
    options.push({
      value: 'action',
      text: '操作'
    })
  }
  if (!card.linkurl && (!card.linkmenu || card.linkmenu.length === 0)) {
    card.perspective = ''
  }
  return [
    {
      type: 'text',
      key: 'label',
      label: '列头文字',
      initVal: card.label,
      required: true
    },
    {
      type: 'select',
      key: 'type',
      label: Formdict['model.form.type'],
      initVal: card.type,
      required: true,
      options: options
    },
    {
      type: 'select',
      key: 'field',
      label: Formdict['model.form.field'],
      initVal: card.field,
      required: true,
      options: fields
    },
    {
      type: 'select',
      key: 'nameField',
      label: Formdict['model.name'] + Formdict['model.form.field'],
      initVal: card.nameField || '',
      required: false,
      options: [{uuid: 'empty', field: '', label: '空'}, ...fields]
    },
    {
      type: 'number',
      key: 'Width',
      min: 20,
      max: 1000,
      decimal: 0,
      label: Formdict['model.form.columnWidth'],
      initVal: card.Width || 120,
      required: true
    },
    {
      type: 'radio',
      key: 'joint',
      label: Formdict['model.form.paramJoint'],
      initVal: card.joint || 'true',
      required: true,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'radio',
      key: 'Hide',
      label: Formdict['model.hidden'],
      initVal: card.Hide || 'false',
      required: true,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'radio',
      key: 'IsSort',
      label: Formdict['model.sort'],
      initVal: card.IsSort || (card.isSub ? 'false' : 'true'),
      required: true,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'radio',
      key: 'Align',
      label: Formdict['model.form.align'],
      initVal: card.Align || 'left',
      required: true,
      options: [{
        value: 'left',
        text: Formdict['model.form.alignLeft']
      }, {
        value: 'center',
        text: Formdict['model.form.alignCenter']
      }, {
        value: 'right',
        text: Formdict['model.form.alignRight']
      }]
    },
    {
      type: 'radio',
      key: 'rowspan',
      label: '行合并',
      initVal: card.rowspan || 'false',
      tooltip: '相邻行信息相同时,单元格合并。',
      required: false,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'radio',
      key: 'sum',
      label: '显示合计',
      initVal: card.sum || 'false',
      tooltip: '合计信息只在使用系统数据源时有效。',
      required: false,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'number',
      key: 'decimal',
      min: 0,
      max: 18,
      decimal: 0,
      label: Formdict['header.form.decimal'],
      initVal: card.decimal || 0,
      required: true
    },
    {
      type: 'select',
      key: 'format',
      label: Formdict['header.form.format'],
      initVal: card.format || 'none',
      options: [{
        value: 'none',
        text: '无'
      }, {
        value: 'thdSeparator',
        text: '千分位'
      }, {
        value: 'percent',
        text: '百分比'
      }, {
        value: 'abs',
        text: '绝对值'
      }],
      required: false
    },
    {
      type: 'select',
      key: 'textFormat',
      label: Formdict['header.form.format'],
      initVal: card.textFormat || 'none',
      options: [{
        value: 'none',
        text: '无'
      }, {
        value: 'encryption',
        text: '加密'
      }, {
        value: 'YYYY-MM-DD',
        text: 'YYYY-MM-DD'
      }, {
        value: 'YYYY-MM-DD HH:mm:ss',
        text: 'YYYY-MM-DD HH:mm:ss'
      }],
      required: false
    },
    {
      type: 'text',
      key: 'prefix',
      label: Formdict['header.form.prefix'],
      initVal: card.prefix || '',
      required: false,
      readonly: false
    },
    {
      type: 'text',
      key: 'postfix',
      label: Formdict['header.form.postfix'],
      initVal: card.postfix || '',
      required: false,
      readonly: false
    },
    {
      type: 'select',
      key: 'lenWidRadio',
      label: '长宽比',
      initVal: card.lenWidRadio || '1:1',
      required: true,
      options: [
        { value: '1:1', text: '1:1' },
        { value: '4:3', text: '4:3' },
        { value: '3:2', text: '3:2' },
        { value: '16:9', text: '16:9' },
        { value: '2:1', text: '2:1' },
        { value: '3:1', text: '3:1' },
        { value: '4:1', text: '4:1' },
        { value: '5:1', text: '5:1' },
        { value: '6:1', text: '6:1' },
        { value: '7:1', text: '7:1' },
        { value: '8:1', text: '8:1' },
        { value: '9:1', text: '9:1' },
        { value: '10:1', text: '10:1' },
        { value: '3:4', text: '3:4' },
        { value: '2:3', text: '2:3' },
        { value: '9:16', text: '9:16' },
      ]
    },
    {
      type: 'radio',
      key: 'picSort',
      label: '图片排列',
      initVal: card.picSort || '1',
      tooltip: '同一单元格内,含有多张图片时的列数。',
      required: false,
      options: [{
        value: '1',
        text: '1'
      }, {
        value: '2',
        text: '2'
      }, {
        value: '3',
        text: '3'
      }, {
        value: '4',
        text: '4'
      }]
    },
    {
      type: 'radio',
      key: 'scale',
      label: '点击缩放',
      initVal: card.scale || 'true',
      required: false,
      options: [{
        value: 'true',
        text: Formdict['model.true']
      }, {
        value: 'false',
        text: Formdict['model.false']
      }]
    },
    {
      type: 'radio',
      key: 'perspective',
      label: '字段透视',
      initVal: card.perspective || '',
      options: [{
        value: '',
        text: '无'
      }, {
        value: 'linkmenu',
        text: '菜单'
      }, {
        value: 'linkurl',
        text: '链接'
      }],
      forbidden: appType === 'mob'
    },
    {
      type: appType === 'pc' ? 'select' : 'cascader',
      key: 'linkmenu',
      label: Formdict['model.menu'],
      initVal: card.linkmenu || (appType === 'pc' ? '' : []),
      required: true,
      options: menulist,
      forbidden: appType === 'mob'
    },
    {
      type: 'textarea',
      key: 'linkurl',
      label: '链接地址',
      initVal: card.linkurl || '',
      required: true,
      forbidden: appType === 'mob'
    },
    {
      type: 'multiselect',
      key: 'linkfields',
      label: '关联字段',
      initVal: card.linkfields || [],
      required: false,
      options: fields,
      forbidden: appType === 'mob'
    },
    {
      type: 'radio',
      key: 'open',
      label: '打开方式',
      initVal: card.open || 'blank',
      required: false,
      forbid: appType !== 'pc',
      options: [
        { value: 'blank', text: '新窗口' },
        { value: 'self', text: '当前窗口' }
      ]
    },
    {
      type: 'radio',
      key: 'eval',
      label: '解析',
      initVal: card.eval || 'true',
      tooltip: '当公式内容涉及计算时请选择“是”,当公式内容为字段拼接时请选择“否”。',
      required: false,
      options: [
        { value: 'true', text: '是' },
        { value: 'false', text: '否' }
      ]
    },
    {
      type: 'textarea',
      key: 'formula',
      label: '公式',
      initVal: card.formula || '',
      tooltip: '执行时会使用查询到的数据替换相应的字段,展示获得的结果,在不使用解析时换行符或空格会替换为页面元素。可使用JS的一些语法,如:三元表达式 @field1@ > @field2@ ? 0 : 1;Math对象,取绝对值 Math.abs(@field@)、四舍五入 Math.round(@field@)等',
      placeholder: '例如:@price@ * @number@',
      required: true
    },
    {
      type: 'multiselect',
      key: 'blacklist',
      label: Formdict['header.form.blacklist'],
      initVal: card.blacklist || [],
      required: false,
      options: roleList,
      forbidden: appType === 'mob'
    }
  ]
}
src/menu/components/table/base-table/columns/editColumn/index.jsx
New file
@@ -0,0 +1,398 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Form, Row, Col, Input, Select, InputNumber, Radio, Tooltip, Cascader, Modal } from 'antd'
import { QuestionCircleOutlined } from '@ant-design/icons'
import { getColumnForm } from './formconfig'
import { formRule } from '@/utils/option.js'
import './index.scss'
const { TextArea } = Input
const columnTypeOptions = {
  text: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'textFormat', 'blacklist', 'perspective', 'rowspan'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'blacklist', 'perspective', 'sum', 'rowspan'],
  link: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'joint', 'Width', 'blacklist', 'nameField'],
  textarea: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'blacklist'],
  picture: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'blacklist', 'scale', 'lenWidRadio', 'picSort'],
  colspan: ['label', 'type', 'Align', 'Hide', 'blacklist'],
  custom: ['label', 'type', 'Align', 'Hide', 'Width', 'blacklist'],
  action: ['label', 'type', 'Align', 'Width'],
  formula: ['label', 'type', 'Align', 'Hide', 'Width', 'prefix', 'postfix', 'eval', 'formula', 'blacklist'],
  index: ['label', 'type', 'Align', 'Width']
}
class NormalTableColumn extends Component {
  static propTpyes = {
    dict: PropTypes.object,     // 字典项
    visible: PropTypes.bool,
    column: PropTypes.object,
    fields: PropTypes.array,
    submitCol: PropTypes.func,  // 提交事件
    cancelCol: PropTypes.func   // 取消时删除事件
  }
  state = {
    visible: false,
    formlist: null
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    if (nextProps.column && !is(fromJS(this.props.column), fromJS(nextProps.column))) {
      this.editColumn(nextProps.column)
    }
  }
  editColumn = (column) => {
    let fields = fromJS(this.props.fields).toJS().map(item => {
      if (item.label.toLowerCase() !== item.field.toLowerCase()) {
        item.text = item.label + '(' + item.field + ')'
      }
      return item
    })
    let formlist = getColumnForm(column, fields)
    let _options = fromJS(columnTypeOptions[column.type]).toJS()
    if (column.type === 'text' || column.type === 'number') {
      if (column.perspective === 'linkmenu') {
        _options.push('linkmenu', 'linkfields', 'open')
      } else if (column.perspective === 'linkurl') {
        _options.push('linkurl', 'linkfields', 'open')
      }
    }
    this.setState({
      visible: true,
      type: column.type,
      formlist: formlist.map(item => {
        item.hidden = !_options.includes(item.key)
        return item
      })
    })
    if (column.focus) {
      setTimeout(() => {
        try {
          let _form = document.getElementById('label')
          _form && _form.select()
        } catch (e) {
          console.warn('表单focus失败!')
        }
      }, 200)
    }
  }
  typeChange = (key, value, option) => {
    if (key === 'type') {
      let _options = fromJS(columnTypeOptions[value]).toJS()
      this.setState({
        type: value,
        formlist: this.state.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          return item
        })
      }, () => {
        if (value === 'link' || value === 'textarea' || value === 'picture') {
          this.props.form.setFieldsValue({IsSort: 'false'})
        } else if (value === 'text' || value === 'number') {
          this.props.form.setFieldsValue({perspective: ''})
        } else if (value === 'action' || value === 'colspan') {
          this.props.form.setFieldsValue({Align: 'center'})
        }
      })
    } else if (key === 'field') {
      let values = {label: option.props.label || option.props.children}
      if (/Decimal|int/ig.test(option.props.datatype)) {
        let decimal = 0
        if (/Decimal/ig.test(option.props.datatype)) {
          decimal = +option.props.datatype.replace(/Decimal\(18,/ig, '').replace(')', '')
        }
        values.type = 'number'
        values.decimal = decimal
      } else {
        values.type = 'text'
      }
      if (values.type !== this.state.type) {
        values.perspective = ''
        let _options = fromJS(columnTypeOptions[values.type]).toJS()
        this.setState({
          type: values.type,
          formlist: this.state.formlist.map(item => {
            item.hidden = !_options.includes(item.key)
            return item
          })
        }, () => {
          this.props.form.setFieldsValue(values)
        })
      } else {
        this.props.form.setFieldsValue(values)
      }
    } else if (key === 'format' && value === 'percent') {
      this.props.form.setFieldsValue({postfix: '%'})
    }
  }
  changeRadio = (key, value) => {
    if (key === 'perspective') {
      let _options = fromJS(columnTypeOptions[this.state.type]).toJS()
      if (value === 'linkmenu') {
        _options.push('linkmenu', 'linkfields', 'open')
      } else if (value === 'linkurl') {
        _options.push('linkurl', 'linkfields', 'open')
      }
      this.setState({
        formlist: this.state.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          return item
        })
      })
    }
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const { formlist } = this.state
    const fields = []
    if (!formlist) return null
    formlist.forEach((item, index) => {
      if (item.hidden || item.forbidden) return
      if (item.type === 'text') {
        let rules = []
        if (item.key !== 'linkurl') {
          rules = [{
            max: formRule.input.max,
            message: formRule.input.message
          }]
        }
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  },
                  ...rules
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} onPressEnter={this.handleSubmit} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'number') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(item.unlimit ? <InputNumber onPressEnter={this.handleSubmit}/> :
                  <InputNumber min={item.min} max={item.max} precision={item.decimal} onPressEnter={this.handleSubmit}/>)}
            </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, option) => {this.typeChange(item.key, value, option)}}
                  getPopupContainer={() => document.getElementById('columnwinter')}
                >
                  {item.options.map((option, index) =>
                    <Select.Option key={index} datatype={option.datatype || ''} label={option.label || ''} value={(option.value || option.field || option.MenuID)}>
                      {(option.text || option.label || option.MenuName)}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : 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.changeRadio(item.key, e.target.value)}}>
                  {
                    item.options.map(option => {
                      return (
                        <Radio key={option.value} value={option.value}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'multiselect') { // 多选
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || []
              })(
                <Select
                  showSearch
                  mode="multiple"
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                >
                  {item.options.map((option, i) =>
                    <Select.Option id={i} key={i} value={option.value || option.field}>{option.text || option.label}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'cascader') { // 多选
        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 + '!'
                  }
                ]
              })(
                <Cascader
                  options={item.options}
                  placeholder=""
                  getPopupContainer={() => document.getElementById('columnwinter')}
                />
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'textarea') {
        fields.push(
          <Col span={24} key={index} className="textarea">
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <QuestionCircleOutlined className="mk-form-tip" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<TextArea rows={2} disabled={item.readonly} placeholder={item.placeholder || ''}/>)}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleSubmit = () => {
    // 表单提交时检查输入值是否正确
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        this.setState({visible: false, formlist: null})
        this.props.submitCol(values)
      }
    })
  }
  editModalCancel = () => {
    this.setState({visible: false, formlist: null})
    this.props.cancelCol()
  }
  render() {
    const { visible } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 6 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 18 }
      }
    }
    return (
      <div style={{display: 'inline-block'}}>
        <Modal
          title="显示列编辑"
          visible={visible}
          width={800}
          maskClosable={false}
          onOk={this.handleSubmit}
          onCancel={this.editModalCancel}
          destroyOnClose
        >
          <Form {...formItemLayout} className="commontable-column-form" id="columnwinter">
            <Row gutter={24}>{this.getFields()}</Row>
          </Form>
        </Modal>
      </div>
    )
  }
}
export default Form.create()(NormalTableColumn)
src/menu/components/table/base-table/columns/editColumn/index.scss
New file
@@ -0,0 +1,17 @@
.commontable-column-form {
  min-height: 190px;
  .ant-cascader-menus {
    padding: 5px 0px;
    .ant-cascader-menu:last-child {
      padding-right: 3px;
    }
  }
  .textarea {
    .ant-form-item-label {
      width: 12%;
    }
    .ant-form-item-control-wrapper {
      width: 88%;
    }
  }
}
src/menu/components/table/base-table/columns/index.jsx
New file
@@ -0,0 +1,618 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { DndProvider, DragSource, DropTarget } from 'react-dnd'
import { Table, Popover, Modal, message } from 'antd'
import { PlusOutlined, FileSyncOutlined, EditOutlined, CopyOutlined, DeleteOutlined, FontColorsOutlined, CloseCircleOutlined, AntDesignOutlined } from '@ant-design/icons'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import MKEmitter from '@/utils/events.js'
import './index.scss'
const { confirm } = Modal
const EditColumn = asyncIconComponent(() => import('./editColumn'))
const MarkColumn = asyncIconComponent(() => import('@/menu/components/share/markcomponent'))
const CardCellComponent = asyncComponent(() => import('@/menu/components/card/cardcellcomponent'))
const MobPagination = asyncIconComponent(() => import('@/menu/components/share/mobPagination'))
const PasteComponent = asyncIconComponent(() => import('@/components/paste'))
class HeaderCol extends Component {
  deleteCol = () => {
    const _this = this
    confirm({
      content: '确定删除显示列吗?',
      onOk() {
        _this.props.deleteCol(_this.props.column)
      },
      onCancel() {}
    })
  }
  updateMarks = (vals) => {
    const { column } = this.props
    this.props.updateCol({...column, marks: vals})
  }
  shouldComponentUpdate (nextProps, nextState) {
    if (this.props.rowSpan !== nextProps.rowSpan || this.props.colSpan !== nextProps.colSpan) {
      return true
    }
    if (!nextProps.column) return false
    return !is(fromJS(this.props.column), fromJS(nextProps.column)) ||
      !is(fromJS(this.props.fields), fromJS(nextProps.fields)) ||
      this.props.index !== nextProps.index
  }
  render() {
    const { connectDragSource, connectDropTarget, moveCol, addElement, updateCol, editColumn, pasteCell, changeStyle, deleteCol, index, column, align, fields, children, ...restProps } = this.props
    if (index !== undefined) {
      return connectDragSource(
        connectDropTarget(<th {...restProps} index={index} style={{ cursor: 'move', textAlign: align }} onDoubleClick={() => column && this.props.editColumn(column)}>
          <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
            <div className="mk-popover-control" onDoubleClick={(e) => e.stopPropagation()}>
              {column && ['custom', 'colspan', 'action'].includes(column.type) ?
                <PlusOutlined className="plus" title="添加" onClick={() => this.props.addElement(column)} /> : null
              }
              <EditOutlined className="edit" title="编辑" onClick={() => this.props.editColumn(column)} />
              {column && column.type === 'custom' ? <PasteComponent options={['customCardElement']} updateConfig={(res, resolve) => this.props.pasteCell(column, res, resolve)} /> : null}
              {column && column.type === 'custom' ? <FontColorsOutlined className="style" title="调整样式" onClick={() => this.props.changeStyle(column)}/> : null}
              <DeleteOutlined className="close" title="删除" onClick={this.deleteCol} />
              {column && ['text', 'number', 'formula'].includes(column.type) ? <MarkColumn columns={fields} marks={column.marks} onSubmit={this.updateMarks} /> : null }
            </div>
          } trigger="hover">
            {children}
          </Popover>
        </th>),
      )
    } else if (column) {
      return (
        <th {...restProps} key={column.uuid} onDoubleClick={() => this.props.editColumn(column)}>
          <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
            <div className="mk-popover-control" onDoubleClick={(e) => e.stopPropagation()}>
              {column && ['custom', 'colspan'].includes(column.type) ?
                <PlusOutlined className="plus" title="添加" onClick={() => this.props.addElement(column)} /> : null
              }
              <EditOutlined className="edit" title="编辑" onClick={() => this.props.editColumn(column)} />
              {column.type === 'custom' ? <PasteComponent options={['customCardElement']} updateConfig={(res, resolve) => this.props.pasteCell(column, res, resolve)} /> : null}
              <DeleteOutlined className="close" title="删除" onClick={this.deleteCol} />
              {column && ['text', 'number', 'formula'].includes(column.type) ? <MarkColumn columns={fields} marks={column.marks} onSubmit={this.updateMarks} /> : null }
            </div>
          } trigger="hover">
            {children}
          </Popover>
        </th>
      )
    } else {
      return (<th {...restProps}>{children}</th>)
    }
  }
}
const rowSource = {
  beginDrag(props) {
    return {
      index: props.index,
    }
  }
}
const ColTarget = {
  drop(props, monitor) {
    const dragIndex = monitor.getItem().index
    const hoverIndex = props.index
    if (dragIndex === undefined || hoverIndex === undefined || dragIndex === hoverIndex) {
      return
    }
    props.moveCol(dragIndex, hoverIndex)
    monitor.getItem().index = hoverIndex
  },
}
const DragableHeaderCol = DropTarget('col', ColTarget, connect => ({
  connectDropTarget: connect.dropTarget()
}))(
  DragSource('col', rowSource, connect => ({
    connectDragSource: connect.dragSource(),
  }))(HeaderCol),
)
class EditableColumnCell extends Component {
  updateCard = (vals, btn) => {
    const { column } = this.props
    this.props.upComponent({...column, elements: vals}, btn)
  }
  shouldComponentUpdate (nextProps, nextState) {
    const { config, column } = this.props
    if (!nextProps.column) return true
    return !is(fromJS(column), fromJS(nextProps.column)) ||
      !is(fromJS(config.columns), fromJS(nextProps.config.columns)) ||
      !is(fromJS(config.action), fromJS(nextProps.config.action)) ||
      !is(fromJS(config.search), fromJS(nextProps.config.search))
  }
  render() {
    const { column, config, children, className, style } = this.props
    if (column && column.type === 'custom') {
      return (
        <td style={{padding: 0, minWidth: column.Width || 100, ...(column.style || {})}} className={className}>
          <CardCellComponent cards={config} cardCell={column} elements={column.elements} updateElement={this.updateCard}/>
        </td>
      )
    } else if (column && column.type === 'action') {
      return (
        <td style={{padding: '0 5px', textAlign: column.Align, minWidth: column.Width || 100}} className={'action-column ' + className}>
          <CardCellComponent cards={config} cardCell={column} elements={column.elements} updateElement={this.updateCard}/>
        </td>
      )
    } else if (column) {
      let val = column.field || ''
      if (column.type === 'index') {
        val = '$Index'
      } else if (column.type === 'formula') {
        val = column.formula
        if (column.eval === 'false') {
          val = val.replace(/\n/ig, '<br/>').replace(/\s/ig, '&nbsp;')
          val = <span style={{fontWeight: 'inherit'}} dangerouslySetInnerHTML={{__html: val}}></span>
        }
      }
      return (
        <td style={{...style, minWidth: column.Width || 100}} className={className}>
          {val}
          {column.Hide === 'true' ? <CloseCircleOutlined style={{marginLeft: '5px', color: 'orange', fontSize: '12px'}}/> : null}
          {column.marks && column.marks.length ? <AntDesignOutlined className="profile"/> : null}
        </td>
      )
    } else {
      return (
        <td style={style} className={className}>
          {children}
        </td>
      )
    }
  }
}
class NormalTableColumns extends Component {
  static propTpyes = {
    config: PropTypes.object,       // 配置信息
    updatecolumn: PropTypes.func    // 数据变化
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    appType: sessionStorage.getItem('appType'),
    tableId: '',
    data: [{uuid: Utils.getuuid()}],
    refresh: false,    // 强制刷新
    columns: [],
    fields: [],
    editStyleCard: null,
    lineMarks: []
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    let tableId = (() => {
      let uuid = []
      let _options = 'abcdefghigklmnopqrstuv'
      for (let i = 0; i < 19; i++) {
        uuid.push(_options.substr(Math.floor(Math.random() * 0x20), 1))
      }
      return uuid.join('')
    }) ()
    this.setState({
      tableId,
      columns: fromJS(config.cols).toJS(),
      fields: fromJS(config.columns).toJS(),
      lineMarks: config.lineMarks ? fromJS(config.lineMarks).toJS() : []
    }, () => {
      const element = document.getElementById(tableId)
      element && element.style.setProperty('--mk-table-border-color', config.wrap.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', config.wrap.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', config.wrap.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', config.wrap.fontWeight || 'normal')
    })
  }
  UNSAFE_componentWillReceiveProps (nextProps) {
    if (!is(fromJS(this.state.columns), fromJS(nextProps.config.cols))) {
      let _columns = fromJS(nextProps.config.cols).toJS()
      this.setState({columns: _columns})
      let lastcol = _columns.slice(-1)[0]
      if (lastcol && lastcol.focus) {
        this.editColumn(lastcol)
      }
    } else if (!is(fromJS(this.state.fields), fromJS(nextProps.config.columns))) {
      this.setState({fields: fromJS(nextProps.config.columns).toJS()})
    } else if (!is(fromJS(this.props.config.wrap), fromJS(nextProps.config.wrap))) {
      const element = document.getElementById(this.state.tableId)
      element && element.style.setProperty('--mk-table-border-color', nextProps.config.wrap.borderColor || '#e8e8e8')
      element && element.style.setProperty('--mk-table-color', nextProps.config.wrap.color || 'rgba(0, 0, 0, 0.65)')
      element && element.style.setProperty('--mk-table-font-size', nextProps.config.wrap.fontSize || '14px')
      element && element.style.setProperty('--mk-table-font-weight', nextProps.config.wrap.fontWeight || 'normal')
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    const { config } = this.props
    return !is(fromJS(this.state), fromJS(nextState)) ||
      !is(fromJS(config.wrap), fromJS(nextProps.config.wrap)) ||
      !is(fromJS(config.search), fromJS(nextProps.config.search)) ||
      !is(fromJS(config.action), fromJS(nextProps.config.action)) ||
      config.setting.laypage !== nextProps.config.setting.laypage
  }
  moveCol = (dragIndex, hoverIndex) => {
    let _columns = fromJS(this.state.columns).toJS()
    _columns.splice(hoverIndex, 0, ..._columns.splice(dragIndex, 1))
    this.setState({
      columns: _columns
    }, () => {
      this.props.updatecolumn({...this.props.config, cols: _columns})
    })
  }
  loopCol = (columns, col) => {
    return columns.map(column => {
      if (column.type === 'colspan') {
        column.subcols = this.loopCol(column.subcols, col)
      }
      if (column.uuid === col.uuid) {
        return col
      }
      return column
    })
  }
  updateCol = (col, btn) => {
    let _columns = fromJS(this.state.columns).toJS()
    _columns = this.loopCol(_columns, col)
    this.setState({
      columns: _columns,
    }, () => {
      let config = {...this.props.config, cols: _columns}
      if (btn) {
        config.action = config.action.filter(item => item.uuid !== btn.uuid)
      }
      this.props.updatecolumn(config)
    })
  }
  editColumn = (col) => {
    this.setState({
      card: fromJS(col).toJS()
    })
  }
  pasteCell = (col, cell, resolve) => {
    resolve({status: true})
    delete cell.copyType
    cell.uuid = Utils.getuuid()
    cell.focus = true
    MKEmitter.emit('cardAddElement', [this.props.config.uuid, col.uuid], cell)
  }
  addElement = (col) => {
    const { config } = this.props
    let column = fromJS(col).toJS()
    if (column.type === 'colspan') {
      column.subcols = column.subcols || []
      let subcol = { isSub: true, focus: true, uuid: Utils.getuuid(), label: 'label', field: '', type: 'text' }
      column.subcols.push(subcol)
      this.setState({
        card: subcol
      })
      this.updateCol(column)
    } else if (column.type === 'custom') {
      let newcard = {uuid: Utils.getuuid(), focus: true, width: 24, eleType: 'text', datatype: 'dynamic', style: {paddingLeft: '4px'}}
      // 注册事件-添加元素
      MKEmitter.emit('cardAddElement', [config.uuid, column.uuid], newcard)
    } else if (column.type === 'action') {
      let newcard = {
        uuid: Utils.getuuid(),
        focus: true,
        eleType: 'button',
        label: 'button',
        OpenType: 'prompt',
        class: 'primary',
        intertype: 'system',
        execSuccess: 'grid',
        execError: 'never',
        show: 'link'
      }
      // 注册事件-添加元素
      MKEmitter.emit('cardAddElement', [config.uuid, column.uuid], newcard)
    }
  }
  submitCol = (col) => {
    const { card } = this.state
    col.uuid = card.uuid
    col.isSub = card.isSub === true
    col.marks = card.marks || []
    if (col.type === 'colspan') {
      col.subcols = card.subcols || []
    } else if (col.type === 'custom') {
      col.elements = card.type === 'custom' ? (card.elements || []) : []
    } else if (col.type === 'action') {
      col.elements = card.type === 'action' ? (card.elements || []) : []
    }
    this.setState({card: null})
    this.updateCol(col)
  }
  changeStyle = (col) => {
    this.setState({
      editStyleCard: fromJS(col).toJS()
    })
    MKEmitter.emit('changeStyle', ['font', 'padding'], col.style || {}, this.getStyle)
  }
  getStyle = (style) => {
    const { editStyleCard } = this.state
    let _card = {...editStyleCard, style}
    this.updateCol(_card)
  }
  cancelCol = () => {
    const { card } = this.state
    if (card.focus) {
      this.deleteCol(card)
    }
    this.setState({card: null})
  }
  loopDelCol = (columns, col) => {
    return columns.filter(column => {
      if (column.type === 'colspan') {
        column.subcols = this.loopDelCol(column.subcols, col)
      }
      return column.uuid !== col.uuid
    })
  }
  deleteCol = (col) => {
    const { appType } = this.state
    let _columns = fromJS(this.state.columns).toJS()
    _columns = this.loopDelCol(_columns, col)
    this.setState({
      columns: _columns
    }, () => {
      this.props.updatecolumn({...this.props.config, cols: _columns})
    })
    if (col.type !== 'action' || appType === 'mob') return
    let uuids = []
    col.elements && col.elements.forEach(c => {
      if (appType === 'pc' && c.OpenType !== 'popview') return
      uuids.push(c.uuid)
    })
    if (uuids.length === 0) return
    MKEmitter.emit('delButtons', uuids)
  }
  updateLineMarks = (vals) => {
    this.setState({
      lineMarks: vals
    }, () => {
      this.props.updatecolumn({...this.props.config, lineMarks: vals})
    })
  }
  /**
   * @description 显示列复制
   */
  copycolumn = () => {
    const { columns } = this.state
    let oInput = document.createElement('input')
    let val = {
      copyType: 'cols',
      cols: columns.filter(col => !col.origin)
    }
    let srcid = localStorage.getItem(window.location.href.split('#')[0] + 'srcId')
    if (srcid) {
      val.$srcId = srcid
    }
    oInput.value = window.btoa(window.encodeURIComponent(JSON.stringify(val)))
    document.body.appendChild(oInput)
    oInput.select()
    document.execCommand('Copy')
    oInput.className = 'oInput'
    oInput.style.display = 'none'
    message.success('复制成功。')
    document.body.removeChild(oInput)
  }
  handlecolumns = (columns, fields, config, isSub) => {
    return columns.map((col, index) => {
      return {
        title: col.label,
        dataIndex: col.uuid,
        align: col.Align,
        // sorter: !isSub && col.IsSort === 'true',
        sorter: col.IsSort === 'true',
        onCell: () => ({
          column: col,
          width: col.Width,
          config: config,
          upComponent: this.updateCol
        }),
        onHeaderCell: () => ({
          index: isSub ? undefined : index,
          column: col,
          fields: fields,
          align: col.Align,
          moveCol: this.moveCol,
          updateCol: this.updateCol,
          addElement: this.addElement,
          editColumn: this.editColumn,
          pasteCell: this.pasteCell,
          changeStyle: this.changeStyle,
          deleteCol: this.deleteCol,
        }),
        children: col.subcols && col.subcols.length ? this.handlecolumns(col.subcols, fields, config, true) : null,
      }
    })
  }
  syncfield = () => {
    const { fields } = this.state
    let columns = fromJS(this.state.columns).toJS()
    columns = columns.filter(c => !c.origin)
    let keys = columns.map(col => col.field)
    fields.forEach(item => {
      if (keys.includes(item.field)) return
      let cell = { uuid: Utils.getuuid(), label: item.label, field: item.field, Align: 'left', Hide: 'false', IsSort: 'true', Width: 120, blacklist: [], postfix: '', prefix: '', linkmenu: [], marks: [], perspective: 'linkmenu' }
      if (/Nvarchar|date/ig.test(item.datatype)) {
        cell.type = 'text'
        cell.rowspan = 'false'
        cell.textFormat = 'none'
      } else {
        cell.type = 'number'
        cell.format = 'none'
        cell.sum = 'false'
        cell.decimal = item.decimal || 0
        cell.Width = 80
      }
      columns.push(cell)
    })
    const _this = this
    confirm({
      content: '确定同步字段集吗?',
      onOk() {
        _this.setState({columns}, () => {
          _this.props.updatecolumn({..._this.props.config, cols: columns})
        })
      },
      onCancel() {}
    })
  }
  clear = () => {
    const _this = this
    confirm({
      content: '确定清空显示列吗?',
      onOk() {
        _this.setState({columns: []}, () => {
          _this.props.updatecolumn({..._this.props.config, cols: []})
        })
      },
      onCancel() {}
    })
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  render() {
    const { config } = this.props
    const { fields, card, lineMarks, dict, tableId, appType } = this.state
    const components = {
      header: {
        cell: DragableHeaderCol
      },
      body: {
        cell: EditableColumnCell
      }
    }
    const columns = this.handlecolumns(this.state.columns, fields, config)
    return (
      <div className={`normal-table-columns ${config.setting.laypage} ${config.wrap.tableType} ${config.wrap.mode || ''}`} id={tableId}>
        <div className="col-control">
          <CopyOutlined title="复制显示列" onClick={this.copycolumn} />
          <MarkColumn columns={fields} type="line" marks={lineMarks} onSubmit={this.updateLineMarks} />
          <FileSyncOutlined title="同步字段集" onClick={this.syncfield} />
          <DeleteOutlined title="清空显示列" onClick={this.clear}/>
        </div>
        <DndProvider>
          <Table
            rowKey="uuid"
            size={config.wrap.size || 'middle'}
            rowClassName="editable-row"
            bordered={config.wrap.bordered !== 'false'}
            components={components}
            dataSource={this.state.data}
            rowSelection={config.wrap.tableType ? { type: 'radio' } : null}
            columns={columns}
            pagination={appType !== 'mob' ? {
              current: 1,
              pageSize: 10,
              pageSizeOptions: ['10', '25', '50', '100', '500', '1000'],
              showSizeChanger: true,
              total: 58,
              showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条`
            } : false}
          />
          {appType === 'mob' && config.setting.laypage !== 'fasle' ? <MobPagination /> : null}
        </DndProvider>
        <EditColumn column={card} dict={dict} fields={fields} submitCol={this.submitCol} cancelCol={this.cancelCol}/>
      </div>
    )
  }
}
export default NormalTableColumns
src/menu/components/table/base-table/columns/index.scss
New file
@@ -0,0 +1,146 @@
.normal-table-columns {
  position: relative;
  --mk-table-border-color: #e8e8e8;
  --mk-table-color: rgba(0, 0, 0, 0.65);
  --mk-table-font-size: 14px;
  --mk-table-font-weight: normal;
  .ant-table {
    color: inherit;
    font-size: inherit;
    font-weight: inherit;
  }
  .ant-table-body {
    overflow-x: auto;
    tr {
      td {
        background: #ffffff;
      }
      td:not(.ant-table-selection-column) {
        position: relative;
        padding: 12px 8px;
        >.profile {
          position: absolute;
          top: 2px;
          right: 2px;
          color: purple;
          font-size: 12px;
        }
      }
    }
    .action-column {
      .card-detail-row:empty {
        min-height: 40px;
      }
    }
    tr:hover td {
      background: #ffffff!important;
    }
  }
  .ant-table-thead {
    th {
      position: relative;
      .ant-table-column-sorter::before {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        content: '';
      }
    }
    > tr > th {
      padding: 12px 8px;
      .ant-table-column-sorter .ant-table-column-sorter-inner {
        .ant-table-column-sorter-up.on, .ant-table-column-sorter-down.on {
          color: unset;
        }
      }
    }
  }
  .col-control {
    position: absolute;
    z-index: 2;
    right: 0;
    top: -25px;
    >.anticon, >div > .anticon {
      font-size: 16px;
      margin-right: 10px;
      cursor: pointer;
    }
    >.anticon-copy {
      color: #26C281;
    }
    >.anticon-delete {
      color: #ff4d4f;
    }
    >.anticon-file-sync {
      color: #1890ff;
    }
    >div >.profile {
      color: purple;
    }
  }
  .ant-table-small > .ant-table-content > .ant-table-body {
    margin: 0;
  }
  table, tr, th, td {
    border-color: var(--mk-table-border-color)!important;
  }
  table tr {
    th .ant-table-column-title {
      // color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
    td {
      color: var(--mk-table-color)!important;
      font-size: var(--mk-table-font-size)!important;
      font-weight: var(--mk-table-font-weight)!important;
    }
  }
}
.normal-table-columns.false {
  .ant-pagination {
    display: none;
  }
}
.normal-table-columns.checkbox {
  .ant-radio-inner {
    border-radius: 0;
  }
  .ant-radio-inner::after {
    border-radius: 0;
  }
  .ant-radio-checked::after {
    border-radius: 0;
  }
}
.normal-table-columns.ghost {
  .ant-table-thead > tr {
    > th {
      color: inherit;
      background: transparent;
      .ant-table-column-sorter .ant-table-column-sorter-inner {
        color: inherit;
      }
    }
    > th:hover {
      background: transparent;
    }
  }
  .ant-table-body {
    overflow-x: auto;
    tr {
      td {
        background: transparent;
      }
    }
    tr:hover td {
      background: transparent!important;
    }
  }
}
src/menu/components/table/base-table/index.jsx
New file
@@ -0,0 +1,554 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Popover } from 'antd'
import { PlusOutlined, PlusCircleOutlined, PlusSquareOutlined, EditOutlined, ToolOutlined, DeleteOutlined, FontColorsOutlined } from '@ant-design/icons'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
import { resetStyle } from '@/utils/utils-custom.js'
import MKEmitter from '@/utils/events.js'
import getWrapForm from './options'
import Utils from '@/utils/utils.js'
import './index.scss'
const SettingComponent = asyncIconComponent(() => import('@/menu/datasource'))
const NormalForm = asyncIconComponent(() => import('@/components/normalform'))
const SearchComponent = asyncComponent(() => import('@/templates/sharecomponent/searchcomponent'))
const ActionComponent = asyncComponent(() => import('@/menu/components/share/actioncomponent'))
const NormalHeader = asyncComponent(() => import('@/menu/components/share/normalheader'))
const CopyComponent = asyncIconComponent(() => import('@/menu/components/share/copycomponent'))
const UserComponent = asyncIconComponent(() => import('@/menu/components/share/usercomponent'))
const PasteComponent = asyncIconComponent(() => import('@/menu/components/share/pastecomponent'))
// const LogComponent = asyncIconComponent(() => import('@/menu/components/share/logcomponent'))
const ColumnComponent = asyncComponent(() => import('./columns'))
class TableCardEditComponent extends Component {
  static propTpyes = {
    card: PropTypes.object,
    deletecomponent: PropTypes.func,
    updateConfig: PropTypes.func,
  }
  state = {
    appType: sessionStorage.getItem('appType'),
    card: null,
    back: false
  }
  UNSAFE_componentWillMount () {
    const { card } = this.props
    const { appType } = this.state
    if (card.isNew) {
      let _card = {
        uuid: card.uuid,
        type: card.type,
        tabId: card.tabId || '',
        parentId: card.parentId || '',
        format: 'array',    // 组件属性 - 数据格式
        pageable: true,     // 组件属性 - 是否可分页
        switchable: true,   // 组件属性 - 数据是否可切换
        dataName: card.dataName || '',
        width: card.width || 24,
        search: [
          { origin: true, uuid: Utils.getuuid(), label: 'label', field: '', type: 'text', match: 'like' },
          { origin: true, uuid: Utils.getuuid(), label: 'label', field: '', type: 'select', match: 'equal' }
        ],
        action: [
          { origin: true, uuid: Utils.getuuid(), label: '添加', intertype: 'system', OpenType: 'pop', execSuccess: 'grid', icon: 'plus', class: 'green', style: {color: 'rgb(255, 255, 255)', background: 'rgb(38, 194, 129)', marginRight: '15px'} },
          { origin: true, uuid: Utils.getuuid(), label: '修改', intertype: 'system', OpenType: 'pop', execSuccess: 'grid', icon: 'form', class: 'purple', style: {color: 'rgb(255, 255, 255)', background: 'rgb(142, 68, 173)', marginRight: '15px'} },
          { origin: true, uuid: Utils.getuuid(), label: '删除', intertype: 'system', OpenType: 'prompt', execSuccess: 'grid', Ot: 'required', icon: 'delete', class: 'danger', style: {color: 'rgb(255, 255, 255)', background: 'rgb(255, 77, 79)', marginRight: '15px'} }
        ],
        name: card.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        wrap: { name: card.name, width: card.width || 24, bordered: 'true', tableType: 'checkbox', show: 'true' },
        style: { marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px' },
        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
        columns: [],
        cols: [
          { origin: true, uuid: Utils.getuuid(), Align: 'left', label: 'label1', field: '', Hide: 'false', type: 'text', Width: 120 },
          { origin: true, uuid: Utils.getuuid(), Align: 'left', label: 'label2', field: '', Hide: 'false', IsSort: 'true', type: 'text', Width: 120 },
          { origin: true, uuid: Utils.getuuid(), Align: 'left', label: 'label3', field: '', Hide: 'false', IsSort: 'true', type: 'text', Width: 120 },
        ],
        scripts: [],
        btnlog: [],
        isNew: true
      }
      if (card.config) {
        let config = fromJS(card.config).toJS()
        _card.wrap = config.wrap
        _card.wrap.name = card.name
        _card.style = config.style
        _card.headerStyle = config.headerStyle
        _card.setting = config.setting
        _card.columns = config.columns
        _card.scripts = config.scripts
        let oriUids = {}
        _card.action = config.action.map(item => {
          let _uuid = Utils.getuuid()
          oriUids[item.uuid] = _uuid
          item.uuid = _uuid
          return item
        })
        _card.search = config.search.map(item => {
          item.uuid = Utils.getuuid()
          return item
        })
        _card.cols = config.cols.map(col => {
          col.uuid = Utils.getuuid()
          if (col.type === 'colspan' && col.subcols) {
            col = this.loopCol(col)
          } else if (col.type === 'custom' && col.elements) {
            col.elements = col.elements.map(cell => {
              cell.uuid = Utils.getuuid()
              return cell
            })
          } else if (col.type === 'action' && col.elements) {
            col.elements = col.elements.map(cell => {
              cell.uuid = Utils.getuuid()
              return cell
            })
          }
          return col
        })
        if (_card.wrap.doubleClick) {
          _card.wrap.doubleClick = oriUids[_card.wrap.doubleClick] || ''
        }
      }
      if (appType === 'mob') {
        _card.search = []
        _card.action = _card.action.filter(a => !a.origin)
      }
      this.updateComponent(_card)
    } else {
      let _card = fromJS(card).toJS()
      if (appType === 'mob') {
        _card.search = []
      }
      this.setState({
        card: _card
      })
    }
  }
  componentDidMount () {
    MKEmitter.addListener('submitModal', this.handleSave)
    // MKEmitter.addListener('logButton', this.logButton)
    MKEmitter.addListener('completeSave', this.completeSave)
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('submitModal', this.handleSave)
    // MKEmitter.removeListener('logButton', this.logButton)
    MKEmitter.removeListener('completeSave', this.completeSave)
  }
  completeSave = () => {
    const { card } = this.state
    if (card.isNew) {
      let item = fromJS(card).toJS()
      item.search = item.search.filter(a => !a.origin)
      item.action = item.action.filter(a => !a.origin)
      item.cols = item.cols.filter(a => !a.origin)
      delete item.isNew
      this.setState({card: item}, () => { MKEmitter.emit('revert') })
    }
  }
  loopCol = (col) => {
    col.subcols = col.subcols.map(c => {
      c.uuid = Utils.getuuid()
      if (c.type === 'colspan' && c.subcols) {
        c = this.loopCol(c)
      } else if (c.type === 'custom' && c.elements) {
        c.elements = c.elements.map(cell => {
          cell.uuid = Utils.getuuid()
          return cell
        })
      }
      return c
    })
    return col
  }
  /**
   * @description 卡片行外层信息更新(数据源,样式等)
   */
  updateComponent = (card) => {
    card.width = card.wrap.width
    card.name = card.wrap.name
    if (!window.GLOB.styling || !card.errors) { // 样式修改时不做筛查
      card.errors = []
      // let supModule = card.setting.supModule ? card.setting.supModule[card.setting.supModule.length - 1] || '' : ''
      // if (supModule === 'empty') {
      //   supModule = ''
      // }
      let doubleClick = card.wrap.doubleClick || ''
      let columns = card.columns.map(c => c.field)
      // let lowcols = card.columns.map(c => c.field.toLowerCase())
      if (card.setting.interType === 'system' && card.setting.execute !== 'false' && !card.setting.dataresource) {
        card.errors.push({ level: 0, detail: '未设置数据源!'})
      } else if (card.setting.interType === 'system' && card.setting.execute === 'false' && card.scripts.filter(script => script.status !== 'false').length === 0) {
        card.errors.push({ level: 0, detail: '数据源中无可用脚本!'})
      } else if (!card.setting.primaryKey) {
        card.errors.push({ level: 0, detail: '未设置主键!'})
      } else if (!columns.includes(card.setting.primaryKey)) {
        card.errors.push({ level: 0, detail: '主键已失效!'})
      } else if (!card.setting.supModule) {
        card.errors.push({ level: 0, detail: '未设置上级组件!'})
      }
      card.action.forEach(cell => {
        if (cell.OpenType === 'pop' || (cell.OpenType === 'funcbutton' && cell.execMode === 'pop')) {
          if (!cell.modal || cell.modal.fields.length === 0) {
            card.errors.push({ level: 1, detail: `按钮“${cell.label}”中表单尚未添加`})
          // } else {
          //   cell.modal.fields.forEach(m => {
          //     if (m.type === 'linkMain' && !supModule) {
          //       card.errors.push({ level: 1, detail: `按钮“${cell.label}”中关联主表表单“${m.label}”无效`})
          //     } else if (m.field && !columns.includes(m.field) && lowcols.includes(m.field.toLowerCase())) {
          //       card.errors.push({ level: 1, detail: `按钮“${cell.label}”中表单“${m.label}”大小写与字段集不一致`})
          //     }
          //   })
          }
        }
        if (doubleClick === cell.uuid) {
          doubleClick = ''
        }
      })
      card.cols.forEach(col => {
        if (col.type === 'action') {
          col.elements.forEach(cell => {
            if (cell.OpenType === 'pop' || (cell.OpenType === 'funcbutton' && cell.execMode === 'pop')) {
              if (!cell.modal || cell.modal.fields.length === 0) {
                card.errors.push({ level: 1, detail: `按钮“${cell.label}”中表单尚未添加`})
              // } else {
              //   cell.modal.fields.forEach(m => {
              //     if (m.type === 'linkMain' && !supModule) {
              //       card.errors.push({ level: 1, detail: `按钮“${cell.label}”中关联主表表单“${m.label}”无效`})
              //     } else if (m.field && !columns.includes(m.field) && lowcols.includes(m.field.toLowerCase())) {
              //       card.errors.push({ level: 1, detail: `按钮“${cell.label}”中表单“${m.label}”大小写与字段集不一致`})
              //     }
              //   })
              }
            }
            if (doubleClick === cell.uuid) {
              doubleClick = ''
            }
          })
        } else if (col.type === 'custom') {
          col.elements.forEach(cell => {
            if (cell.datatype === 'dynamic' && cell.field && !columns.includes(cell.field)) {
              card.errors.push({ level: 1, detail: `显示列“${col.label}”中动态字段“${cell.field}”无效`})
            }
          })
        } else if (col.field && !columns.includes(col.field)) {
          card.errors.push({ level: 1, detail: `显示列“${col.label}”中字段“${col.field}”无效`})
        }
      })
      if (doubleClick) {
        card.errors.push({ level: 1, detail: `绑定的双击按钮已删除`})
      }
    }
    this.setState({
      card: card
    })
    let _card = card.isNew ? fromJS(card).toJS() : card
    if (_card.isNew) {
      _card.cols = _card.cols.filter(a => !a.origin)
      _card.search = _card.search.filter(a => !a.origin)
      _card.action = _card.action.filter(a => !a.origin)
      delete _card.isNew
    }
    this.props.updateConfig(_card)
  }
  // logButton = (id, item) => {
  //   const { card } = this.state
  //   if (id !== card.uuid) return
  //   let btnlog = card.btnlog || []
  //   btnlog.push(item)
  //   this.updateComponent({...card, btnlog})
  // }
  changeStyle = () => {
    const { card } = this.state
    let style = {...card.style}
    style.color = card.wrap.color || 'rgba(0, 0, 0, 0.65)'
    style.fontSize = card.wrap.fontSize || 14
    style.fontWeight = card.wrap.fontWeight || 'normal'
    MKEmitter.emit('changeStyle', ['font1', 'background', 'border', 'padding', 'margin', 'shadow'], style, this.getStyle)
  }
  getStyle = (style) => {
    const { card } = this.state
    let _card = fromJS(card).toJS()
    let _style = fromJS(style).toJS()
    let color = style.color
    let fontSize = style.fontSize
    let fontWeight = style.fontWeight
    delete _style.color
    delete _style.fontSize
    delete _style.fontWeight
    _card.style = _style
    _card.wrap.color = color
    _card.wrap.fontSize = fontSize
    _card.wrap.fontWeight = fontWeight
    this.updateComponent(_card)
  }
  addColumns = () => {
    let card = fromJS(this.state.card).toJS()
    card.cols.push({ focus: true, uuid: Utils.getuuid(), label: 'label', field: '', type: 'text' })
    this.setState({card})
  }
  addSearch = () => {
    const { card } = this.state
    MKEmitter.emit('plusSearch', card.uuid, {uuid: Utils.getuuid(), focus: true, label: 'label', type: 'text', match: '='}, 'simple')
  }
  addButton = () => {
    const { card } = this.state
    let newcard = {}
    newcard.uuid = Utils.getuuid()
    newcard.focus = true
    newcard.label = 'label'
    newcard.sqlType = ''
    newcard.Ot = 'requiredSgl'
    newcard.OpenType = 'pop'
    newcard.icon = ''
    newcard.class = 'green'
    newcard.intertype = card.setting.interType || 'system'
    newcard.innerFunc = card.setting.innerFunc || ''
    newcard.sysInterface = card.setting.sysInterface || ''
    newcard.outerFunc = card.setting.outerFunc || ''
    newcard.interface = card.setting.interface || ''
    newcard.execSuccess = 'grid'
    newcard.execError = 'never'
    newcard.verify = null
    newcard.show = 'button'
    newcard.style = {marginRight: '15px'}
    // 注册事件-添加按钮
    MKEmitter.emit('addButton', card.uuid, newcard)
  }
  setSubConfig = (item) => {
    const { card, appType } = this.state
    let btn = fromJS(item).toJS()
    if (btn.OpenType === 'pop' || btn.execMode === 'pop') {
      if (!btn.modal) {
        btn.modal = {
          setting: { title: btn.label, width: appType === 'mob' ? 100 : 60, cols: '2', container: 'view', focus: '', finish: 'close', clickouter: 'unclose', display: 'modal' },
          tables: [],
          groups: [],
          fields: []
        }
      }
      MKEmitter.emit('changeModal', card, btn)
    } else if (btn.OpenType === 'popview') {
      MKEmitter.emit('changePopview', card, btn)
    }
  }
  handleSave = (_cards, btn, modal) => {
    let card = fromJS(this.state.card).toJS()
    if (card.uuid !== _cards.uuid) return
    let _index = card.action.findIndex(cell => cell.uuid === btn.uuid)
    if (_index === -1) return
    card.action = card.action.map(cell => {
      if (cell.uuid === btn.uuid) {
        cell.modal = modal
      }
      return cell
    })
    this.updateComponent(card)
  }
  // handleLog = (type, logs, item) => {
  //   let card = fromJS(this.state.card).toJS()
  //   if (type === 'revert') {
  //     let done = false
  //     if (item.$parentId) {
  //       card.cols.forEach(col => {
  //         if (col.type !== 'action') return
  //         if (item.$parentId === col.uuid) {
  //           col.elements = col.elements ? [...col.elements, item] : [item]
  //           done = true
  //         }
  //       })
  //     }
  //     if (!done) {
  //       card.action = card.action ? [...card.action, item] : [item]
  //     }
  //     card.btnlog = logs
  //     this.updateComponent(card)
  //     notification.success({
  //       top: 92,
  //       message: '恢复成功!',
  //       duration: 2
  //     })
  //   } else {
  //     card.btnlog = logs
  //     this.updateComponent(card)
  //     notification.success({
  //       top: 92,
  //       message: '清除成功!',
  //       duration: 2
  //     })
  //   }
  // }
  getWrapForms = () => {
    const { wrap, action, columns, cols } = this.state.card
    let _actions = [...action]
    cols.forEach(col => {
      if (col.type !== 'action') return
      _actions.push(...col.elements)
    })
    return getWrapForm(wrap, _actions, columns)
  }
  updateWrap = (res) => {
    const { card } = this.state
    res.color = card.wrap.color
    res.fontSize = card.wrap.fontSize
    res.fontWeight = card.wrap.fontWeight
    res.show = card.wrap.show || 'true'
    res.advanceType = card.wrap.advanceType || 'modal'
    res.advanceWidth = card.wrap.advanceWidth || 1000
    res.drawerPlacement = card.wrap.drawerPlacement || 'right'
    res.searchRatio = card.wrap.searchRatio || 6
    res.searchLwidth = card.wrap.searchLwidth !== undefined ? card.wrap.searchLwidth : 33.3
    this.updateComponent({...card, wrap: res})
  }
  clickComponent = (e) => {
    if (sessionStorage.getItem('style-control') === 'true' || sessionStorage.getItem('style-control') === 'component') {
      e.stopPropagation()
      MKEmitter.emit('clickComponent', this.state.card)
    }
  }
  render() {
    const { card, appType } = this.state
    let options = ['action', 'search', 'form', 'cols']
    let _style = resetStyle(card.style)
    if (appType === 'mob') {
      options = ['action', 'cols']
    }
    return (
      <div className="menu-normal-table-edit-box" style={_style} onClick={this.clickComponent} id={card.uuid}>
        <NormalHeader hideSearch="true" config={card} updateComponent={this.updateComponent}/>
        <Popover overlayClassName="mk-popover-control-wrap" mouseLeaveDelay={0.2} mouseEnterDelay={0.2} content={
          <div className="mk-popover-control">
            <PlusOutlined className="plus" title="添加列" onClick={this.addColumns}/>
            {appType !== 'mob' ? <PlusCircleOutlined className="plus" title="添加搜索" onClick={this.addSearch}/> : null}
            <PlusSquareOutlined className="plus" title="添加按钮" onClick={this.addButton}/>
            <NormalForm title="表格设置" width={800} update={this.updateWrap} getForms={this.getWrapForms}>
              <EditOutlined style={{color: '#1890ff'}} title="编辑"/>
            </NormalForm>
            <CopyComponent type="normaltable" card={card}/>
            <PasteComponent config={card} options={options} updateConfig={this.updateComponent} />
            <FontColorsOutlined className="style" title="调整样式" onClick={this.changeStyle}/>
            {/* <LogComponent btnlog={card.btnlog || []} handlelog={this.handleLog} /> */}
            <UserComponent config={card}/>
            <DeleteOutlined className="close" title="删除组件" onClick={() => this.props.deletecomponent(card.uuid)} />
            <SettingComponent config={card} updateConfig={this.updateComponent} />
          </div>
        } trigger="hover">
          <ToolOutlined />
        </Popover>
        <SearchComponent config={card} updatesearch={this.updateComponent}/>
        <ActionComponent config={card} setSubConfig={this.setSubConfig} updateaction={this.updateComponent}/>
        <ColumnComponent config={card} updatecolumn={this.updateComponent}/>
        <div className="component-name">
          <div className="center">
            <div className="title">{card.name}</div>
            <div className="content">
              {card.errors && card.errors.map((err, index) => {
                if (err.level === 0) {
                  return <span key={index} className="error">{err.detail}</span>
                } else {
                  return <span key={index} className="waring">{err.detail};</span>
                }
              })}
            </div>
          </div>
        </div>
      </div>
    )
  }
}
export default TableCardEditComponent
src/menu/components/table/base-table/index.scss
New file
@@ -0,0 +1,92 @@
.menu-normal-table-edit-box {
  position: relative;
  box-sizing: border-box;
  background: #ffffff;
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  min-height: 100px;
  .model-table-search-list {
    padding: 10px 0px 15px;
    min-height: 65px;
    border-bottom: 1px solid #f0f0f0;
    .page-card {
      background: transparent;
    }
    .quickly-add {
      display: inline-block;
      position: absolute;
      z-index: 3;
      right: 70px;
      bottom: 5px;
      .ant-btn-block {
        background-color: transparent;
        color: #1890ff;
        border: none;
        box-shadow: none !important;
        height: 18px;
        padding: 0 10px;
      }
    }
  }
  .model-table-search-list.length0 {
    min-height: 10px;
    border-bottom: 0;
    >.quickly-add {
      right: unset;
      left: -8px;
      bottom: 0;
    }
    >.ant-row {
      display: none;
    }
    >.ant-switch {
      display: none;
    }
  }
  .model-table-search-list.length0 + .length0 {
    margin-top: -25px;
  }
  .anticon-tool {
    position: absolute;
    z-index: 2;
    font-size: 16px;
    right: 1px;
    top: 1px;
    cursor: pointer;
    padding: 5px;
    background: rgba(255, 255, 255, 0.55);
  }
  .model-menu-action-list {
    line-height: 55px;
    padding: 0px;
    min-height: 55px;
    >.ant-row {
      min-height: 30px;
    }
  }
  .card-add-button {
    text-align: right;
    clear: left;
    .anticon-plus {
      font-size: 20px;
      color: #26C281;
      padding: 5px;
      margin-right: 10px;
    }
  }
  .ant-btn.mk-link {
    padding: 0;
  }
}
.menu-normal-table-edit-box::after {
  display: block;
  content: ' ';
  clear: both;
}
.menu-normal-table-edit-box:hover {
  z-index: 1;
  box-shadow: 0px 0px 4px #1890ff;
}
src/menu/components/table/base-table/options.jsx
New file
@@ -0,0 +1,274 @@
/**
 * @description Wrap表单配置信息
 */
export default function (wrap, action = [], columns = []) {
  let roleList = sessionStorage.getItem('sysRoles')
  let appType = sessionStorage.getItem('appType')
  if (roleList) {
    try {
      roleList = JSON.parse(roleList)
    } catch (e) {
      roleList = []
    }
  } else {
    roleList = []
  }
  const wrapForm = [
    {
      type: 'text',
      field: 'title',
      label: '标题',
      initval: wrap.title || '',
      required: false
    },
    {
      type: 'text',
      field: 'name',
      label: '组件名称',
      initval: wrap.name || '',
      tooltip: '用于组件间的区分。',
      required: true
    },
    {
      type: 'number',
      field: 'width',
      label: '宽度',
      initval: wrap.width || 24,
      tooltip: '栅格布局,每行等分为24列。',
      min: 1,
      max: 24,
      precision: 0,
      required: true
    },
    {
      type: 'number',
      field: 'height',
      label: '高度',
      initval: wrap.height || '',
      tooltip: '表格高度,空值时高度自适应。',
      min: 10,
      max: 3000,
      precision: 0,
      required: false,
      forbid: appType === 'mob'
    },
    {
      type: 'radio',
      field: 'tableType',
      label: '表格属性',
      initval: wrap.tableType || '',
      required: false,
      options: [
        {value: '', label: '不可选'},
        {value: 'radio', label: '单选'},
        {value: 'checkbox', label: '多选'},
      ],
      controlFields: [
        {field: 'selected', values: ['radio', 'checkbox']},
      ]
    },
    {
      type: 'radio',
      field: 'bordered',
      label: '边框',
      initval: wrap.bordered || 'true',
      required: false,
      options: [
        {value: 'true', label: '有'},
        {value: 'false', label: '无'},
      ]
    },
    {
      type: 'radio',
      field: 'tableHeader',
      label: '表头',
      initval: wrap.tableHeader || 'show',
      required: false,
      options: [
        {value: 'show', label: '显示'},
        {value: 'hidden', label: '隐藏'},
      ]
    },
    {
      type: 'radio',
      field: 'collapse',
      label: '可收起',
      initval: wrap.collapse || 'false',
      required: false,
      options: [
        {value: 'true', label: '是'},
        {value: 'false', label: '否'},
      ],
      forbid: appType === 'mob' || appType === 'pc'
    },
    {
      type: 'radio',
      field: 'size',
      label: '表格大小',
      initval: wrap.size || 'middle',
      tooltip: '表格的内边距,从大到小依次递减。',
      required: false,
      options: [
        {value: 'default', label: '大'},
        {value: 'middle', label: '中'},
        {value: 'small', label: '小'},
        {value: 'mini', label: '迷你'},
      ]
    },
    {
      type: 'radio',
      field: 'mode',
      label: '模式',
      initval: wrap.mode || 'default',
      required: false,
      options: [
        {value: 'default', label: '常规'},
        {value: 'ghost', label: '透明'},
      ]
    },
    {
      type: 'radio',
      field: 'selected',
      label: '首行选中',
      initval: wrap.selected || 'false',
      tooltip: '当按钮执行完成并返回主键值时,默认选中主键值对应行。',
      required: false,
      options: [
        {value: 'false', label: '无'},
        {value: 'init', label: '初始化'},
        {value: 'always', label: '数据加载'},
      ]
    },
    // {
    //   type: 'radio',
    //   field: 'show',
    //   label: '搜索按钮',
    //   initval: wrap.show || 'true',
    //   tooltip: '搜索条件存在时,可选择是否显示搜索按钮。',
    //   required: false,
    //   options: [
    //     {value: 'true', label: '显示'},
    //     {value: 'false', label: '隐藏'},
    //   ]
    // },
    {
      type: 'color',
      field: 'borderColor',
      label: '边框颜色',
      initval: wrap.borderColor || '#e8e8e8',
      tooltip: '默认值 #e8e8e8。',
      required: false
    },
    // {
    //   type: 'color',
    //   field: 'color',
    //   label: '字体颜色',
    //   initval: wrap.color || 'rgba(0, 0, 0, 0.65)',
    //   tooltip: '默认值 rgba(0, 0, 0, 0.65)。',
    //   required: false
    // },
    // {
    //   type: 'number',
    //   field: 'fontSize',
    //   label: '字体大小',
    //   initval: wrap.fontSize || 14,
    //   min: 12,
    //   max: 30,
    //   precision: 0,
    //   required: false
    // },
    // {
    //   type: 'number',
    //   field: 'advanceWidth',
    //   label: '高级搜索',
    //   initval: wrap.advanceWidth || 1000,
    //   tooltip: '高级搜索弹窗的宽度,注:当宽度值小于100时表示占窗口的百分比,大于100时表示宽度的绝对值。',
    //   min: 10,
    //   max: 3000,
    //   precision: 0,
    //   required: false,
    //   forbid: appType === 'mob'
    // },
    {
      type: 'select',
      field: 'doubleClick',
      label: '双击事件',
      initval: wrap.doubleClick || '',
      tooltip: '双击表格中行,触发的按钮。',
      required: false,
      allowClear: true,
      options: action.map(item => ({value: item.uuid, label: item.label})),
      forbid: appType === 'mob'
    },
    {
      type: 'select',
      field: 'controlField',
      label: '禁用字段',
      initval: wrap.controlField || '',
      tooltip: '用于控制行数据是否可选择。',
      required: false,
      allowClear: true,
      options: columns,
      controlFields: [
        {field: 'controlVal', notNull: true},
      ]
    },
    {
      type: 'text',
      field: 'controlVal',
      label: '禁用值',
      initval: wrap.controlVal || '',
      tooltip: '当字段值与禁用值相等时,行数据会禁用,多个值用逗号分隔。',
      required: false
    },
    {
      type: 'radio',
      field: 'empty',
      label: '空值隐藏',
      initval: wrap.empty || 'show',
      tooltip: '当查询数据为空时,隐藏该组件。',
      required: false,
      options: [
        {value: 'show', label: '否'},
        {value: 'hidden', label: '是'},
      ],
    },
    {
      type: 'radio',
      field: 'supKey',
      label: '上级主键',
      initval: wrap.supKey || 'true',
      tooltip: '当设置上级组件时,上级主键值为空是否进行数据查询。',
      required: false,
      options: [
        {value: 'true', label: '验证'},
        {value: 'false', label: '忽略'},
      ],
    },
    {
      type: 'radio',
      field: 'permission',
      label: '权限验证',
      initval: wrap.permission || 'false',
      required: false,
      options: [
        {value: 'true', label: '启用'},
        {value: 'false', label: '禁用'},
      ],
      forbid: !appType
    },
    {
      type: 'multiselect',
      field: 'blacklist',
      label: '黑名单',
      initval: wrap.blacklist || [],
      required: false,
      options: roleList,
      forbid: !!appType
    },
  ]
  return wrapForm
}
src/menu/modalconfig/index.scss
@@ -55,41 +55,6 @@
        }
      }
    }
    .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;
        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;
        }
      }
    }
  }
  .tools::-webkit-scrollbar {
    width: 4px;
src/menu/tableshell/card.jsx
New file
@@ -0,0 +1,56 @@
import React from 'react'
import { useDrag, useDrop } from 'react-dnd'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const AntvTabs = asyncComponent(() => import('@/menu/components/tabs/antv-tabs'))
const BaseTable = asyncComponent(() => import('@/menu/components/table/base-table'))
const Card = ({ id, card, moveCard, findCard, delCard, updateConfig }) => {
  const originalIndex = findCard(id).index
  const [{ isDragging }, drag] = useDrag({
    item: { type: 'menu', id, originalIndex },
    collect: monitor => ({
      isDragging: monitor.isDragging(),
    }),
  })
  const [, drop] = useDrop({
    accept: 'menu',
    canDrop: () => true,
    drop: (item) => {
      const { id: draggedId, originalIndex } = item
      if (originalIndex === undefined) {
        item.dropTargetId = id
      } else if (draggedId) {
        if (draggedId === id) return
        const { index: originIndex } = findCard(draggedId)
        if (originIndex === -1) return
        const { index: overIndex } = findCard(id)
        moveCard(draggedId, overIndex)
      }
    }
  })
  let style = { opacity: 1}
  if (isDragging) {
    style = { opacity: 0.3}
  }
  const getCardComponent = () => {
    if (card.type === 'table') {
      return (<BaseTable card={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    } else if (card.type === 'tabs') {
      return (<AntvTabs tabs={card} updateConfig={updateConfig} deletecomponent={delCard}/>)
    }
  }
  return (
    <div className={'ant-col mk-component-card ant-col-' + (card.width || 24)} ref={node => drag(drop(node))} style={style}>
      {getCardComponent()}
    </div>
  )
}
export default Card
src/menu/tableshell/index.jsx
New file
@@ -0,0 +1,132 @@
import React, { useState } from 'react'
import { useDrop } from 'react-dnd'
import update from 'immutability-helper'
import { Modal } from 'antd'
import Utils from '@/utils/utils.js'
import Card from './card'
import './index.scss'
const { confirm } = Modal
const Container = ({menu, handleList }) => {
  const [cards, setCards] = useState(menu.components)
  const moveCard = (id, atIndex) => {
    const { card, index } = findCard(id)
    const _cards = update(cards, { $splice: [[index, 1], [atIndex, 0, card]] })
    handleList({...menu, components: _cards})
    setCards(_cards)
  }
  if (menu.components.length > cards.length) {
    setCards(menu.components)
  }
  const findCard = id => {
    const card = cards.filter(c => `${c.uuid}` === id)[0]
    return {
      card,
      index: cards.indexOf(card),
    }
  }
  const updateConfig = (element) => {
    const _cards = cards.map(item => item.uuid === element.uuid ? element : item)
    handleList({...menu, components: _cards})
    setCards(_cards)
  }
  const deleteCard = (id) => {
    const { card } = findCard(id)
    let hasComponent = false
    if (card.type === 'tabs') {
      card.subtabs.forEach(tab => {
        if (tab.components.length > 0) {
          hasComponent = true
        }
      })
    }
    confirm({
      title: `确定删除《${card.name}》吗?`,
      content: hasComponent ? '当前组件中含有子组件!' : '',
      onOk() {
        const _cards = cards.filter(item => item.uuid !== card.uuid)
        handleList({...menu, components: _cards})
        setCards(_cards)
      },
      onCancel() {}
    })
  }
  const [, drop] = useDrop({
    accept: 'menu',
    drop(item) {
      if (item.hasOwnProperty('originalIndex') || item.added) {
        delete item.added // 删除组件添加标记
        return
      }
      let name = ''
      let names = {
        tabs: '标签组'
      }
      let i = 1
      while (!name && names[item.component]) {
        let _name = names[item.component] + i
        if (menu.components.filter(com => com.name === _name).length === 0) {
          name = _name
        }
        i++
      }
      let newcard = {
        uuid: Utils.getuuid(),
        type: item.component,
        subtype: item.subtype,
        config: item.config,
        width: item.width || 24,
        dataName: Utils.getdataName(),
        name: name,
        floor: 1,   // 组件的层级
        isNew: true // 新添加标志,用于初始化
      }
      let targetId = ''
      if (item.dropTargetId) {
        targetId = item.dropTargetId
        delete item.dropTargetId
      } else if (cards.length > 0) {
        targetId = cards.slice(-1)[0].uuid
      }
      const { index: overIndex } = findCard(`${targetId}`)
      const _cards = update(cards, { $splice: [[overIndex + 1, 0, newcard]] })
      handleList({...menu, components: _cards})
      setCards(_cards)
    }
  })
  return (
    <div ref={drop} className="table-shell-inner" style={menu.style}>
      <div className="ant-row">
        {cards.map(card => (
          <Card
            id={card.uuid}
            key={card.uuid}
            card={card}
            moveCard={moveCard}
            delCard={deleteCard}
            findCard={findCard}
            updateConfig={updateConfig}
          />
        ))}
      </div>
    </div>
  )
}
export default Container
src/menu/tableshell/index.scss
New file
@@ -0,0 +1,21 @@
.table-shell-inner {
  min-height: calc(100vh - 100px);
  width: auto!important;
  overflow-x: hidden;
  background-size: 100%;
  .anticon {
    cursor: unset;
  }
  .mk-component-card {
    position: relative;
  }
  .anticon-tool {
    color: rgba(0, 0, 0, 0.55);
  }
  .anticon-tool:hover {
    color: #1890ff;
  }
}
src/mob/modalconfig/index.scss
@@ -57,41 +57,6 @@
        }
      }
    }
    .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;
        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;
        }
      }
    }
  }
  .tools::-webkit-scrollbar {
    width: 4px;
src/mob/searchconfig/index.scss
@@ -20,12 +20,7 @@
    .ant-collapse-borderless {
      background-color: #ffffff;
    }
    .ant-collapse-item {
      border: 0;
    }
    .ant-input-search {
      margin-top: 10px;
    }
    .ant-collapse-item.ant-collapse-item-active {
      border-bottom: 1px solid #d9d9d9;
    }
@@ -57,56 +52,8 @@
        }
      }
    }
    .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;
        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;
        }
      }
    }
  }
  .tools::-webkit-scrollbar {
    width: 4px;
  }
  .tools::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.08);
    background: rgba(0, 0, 0, 0.08);
  }
  .tools::-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);
  }
  .modal-control {
    position: absolute;
    right: 0;
@@ -343,7 +290,6 @@
    background: rgba(0, 0, 0, 0);
  }
}
.modal-fields {
  .ant-modal {
src/templates/calendarconfig/index.scss
@@ -54,37 +54,6 @@
      }
    }
    
    .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;
        }
      }
    }
    .config-button {
      min-width: 65px;
    }
src/templates/comtableconfig/index.scss
@@ -74,37 +74,6 @@
        }
      }
    }
    .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;
        }
      }
    }
    .config-button {
      min-width: 65px;
    }
src/templates/formtabconfig/index.jsx
@@ -5,8 +5,8 @@
import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import moment from 'moment'
import { Button, Card, Modal, Collapse, notification, Spin, Select, List, Empty, Switch, Tooltip } from 'antd'
import { QuestionCircleOutlined, CloseOutlined, RedoOutlined, SettingOutlined, PlusOutlined, DeleteOutlined, EditOutlined, SnippetsOutlined } from '@ant-design/icons'
import { Button, Card, Modal, Collapse, notification, Spin, Switch, Tooltip } from 'antd'
import { QuestionCircleOutlined, RedoOutlined, SettingOutlined, PlusOutlined, DeleteOutlined, EditOutlined, SnippetsOutlined } from '@ant-design/icons'
import Api from '@/api'
import zhCN from '@/locales/zh-CN/model.js'
@@ -22,7 +22,6 @@
import SettingForm from './settingform'
import DragElement from './dragelement'
import GroupForm from './groupform'
import EditCard from '@/templates/zshare/editcard'
import MenuForm from '@/templates/zshare/menuform'
import SourceElement from '@/templates/zshare/dragsource'
@@ -31,7 +30,6 @@
import './index.scss'
const { Panel } = Collapse
const { Option } = Select
const { confirm } = Modal
const ModalForm = asyncComponent(() => import('@/templates/zshare/modalform'))
const CreateFunc = asyncComponent(() => import('@/templates/zshare/createfunc'))
@@ -49,7 +47,6 @@
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,        // 字典
    config: null,            // 页面配置
    modaltype: '',           // 模态框类型,控制模态框显示
    tableVisible: false,     // 数据表字段模态框
    tableColumns: [],        // 表格显示列
    fields: null,            // 搜索条件及显示列,可选字段
    menuformlist: null,      // 基本信息表单字段
@@ -61,7 +58,6 @@
    settingVisible: false,   // 全局配置模态框
    closeVisible: false,     // 关闭模态框
    tables: [],              // 可用表名
    selectedTables: [],      // 已选表名
    originMenu: null,        // 原始菜单
    delActions: [],          // 删除按钮列表
    tabviews: [],            // 所有标签页
@@ -143,7 +139,6 @@
      openEdition: btnTab.open_edition || '',
      columns: columns,
      originMenu: JSON.parse(JSON.stringify(_config)),
      selectedTables: _config.tables,
      menuformlist: [
        {
          type: 'text',
@@ -195,73 +190,6 @@
          duration: 5
        })
      }
    })
    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: 5
          })
        }
      })
      this.setState({
        tableColumns: _columns
      })
    })
    Api.getSystemConfig({func: 'sPC_Get_UserTemp', TypeCharTwo: 'tab'}).then(res => {
@@ -856,7 +784,7 @@
      }
      let _LongParam = ''
      let _config = {...config, tables: this.state.selectedTables}
      let _config = {...config}
      // 数据来源为查询且未设置主键时,启用为false
      if (_config.setting.datatype === 'query' && !_config.setting.primaryKey) {
@@ -1227,7 +1155,7 @@
        onCancel() {}
      })
    } else {
      let _config = {...config, tables: this.state.selectedTables}
      let _config = {...config}
      if (!is(fromJS(_config), fromJS(originMenu))) {
        this.setState({
@@ -1237,174 +1165,6 @@
        this.handleViewBack()
      }
    }
  }
  queryField = (type) => {
    const {selectedTables, tableColumns, config} = this.state
    // 判断是否已选择表名
    if (selectedTables.length === 0) {
      notification.warning({
        top: 92,
        message: '请选择表名!',
        duration: 5
      })
      return
    }
    // 表字段集转为map数据
    let columns = new Map()
    tableColumns.forEach(table => {
      table.columns.forEach(column => {
        columns.set(column.field, column)
      })
    })
    if (type === 'search') {
      // 添加搜索条件,字段集中存在搜索条件字段,使用搜索条件对象替换字段集,设置数据类型
      config.groups.forEach(group => {
        group.sublist.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({
      tableVisible: true,
      fields: [...columns.values()]
    })
  }
  addFieldSubmit = () => {
    const {config} = this.state
    // 字段集为空,关闭弹窗
    if (!this.state.fields || this.state.fields.length === 0) {
      this.setState({
        tableVisible: false,
      })
    }
    // 获取已选字段集合
    let cards = this.refs.searchcard.state.selectCards
    let columnsMap = new Map()
    cards.forEach(card => {
      columnsMap.set(card.field, card)
    })
    let groups = config.groups.map(group => {
      group.sublist = group.sublist.map(item => {
        if (columnsMap.has(item.field)) {
          let cell = columnsMap.get(item.field)
          if (cell.selected && cell.type !== item.type) { // 数据类型修改
            item.type = cell.type
            item.initval = ''
          }
          columnsMap.delete(item.field)
        }
        return item
      })
      return group
    })
    let items =  [...columnsMap.values()].map(item => {
      let newcard = {
        uuid: Utils.getuuid(),
        label: item.label,
        field: item.field,
        initval: '',
        type: item.type,
        resourceType: '0',
        options: [],
        orderType: 'asc'
      }
      return newcard
    })
    groups = groups.map(group => {
      if (group.isDefault) {
        group.sublist = [...group.sublist, ...items]
        group.sublist = group.sublist.filter(item => !item.origin)
      }
      return group
    })
    this.setState({
      config: {...config, groups: groups}
    })
    notification.success({
      top: 92,
      message: '操作成功',
      duration: 2
    })
  }
  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: 5
          })
        }
      })
    }
  }
  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 = () => {
@@ -1493,7 +1253,7 @@
      })
    } else {
      this.menuformRef.handleConfirm().then(res => {
        let _config = {...config, tables: this.state.selectedTables}
        let _config = {...config}
        if (!is(fromJS(originMenu), fromJS(_config))) {
          notification.warning({
@@ -1778,43 +1538,6 @@
                  formlist={this.state.menuformlist}
                  wrappedComponentRef={(inst) => this.menuformRef = inst}
                />
                {/* 表名添加 */}
                <div className="ant-col ant-form-item-label">
                  <label>
                    <Tooltip placement="topLeft" title="此处可以添加配置相关的常用表,在添加搜索条件和显示列时,可通过工具栏中的添加按钮,批量添加表格相关字段。">
                      <QuestionCircleOutlined className="mk-form-tip" />
                      {this.state.dict['header.menu.table.add']}
                    </Tooltip>
                  </label>
                </div>
                <Select
                  showSearch
                  className="tables"
                  style={{ width: '100%' }}
                  optionFilterProp="children"
                  value="请选择表名"
                  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} title={table.TbName} 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 + ')'}
                    <CloseOutlined onClick={() => this.deleteTable(item)}/>
                    <div className="bottom-mask"></div>
                  </List.Item>}
                />}
              </Panel>
              {/* 搜索条件添加 */}
              <Panel header={this.state.dict['header.menu.form']} key="1">
@@ -1823,7 +1546,6 @@
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                <Button type="primary" block onClick={() => this.queryField('search')}>批量添加</Button>
              </Panel>
              {/* 按钮添加 */}
              <Panel header={this.state.dict['header.menu.action']} key="2">
@@ -1974,29 +1696,6 @@
            inputSubmit={this.handleSubmit}
            wrappedComponentRef={(inst) => this.actionFormRef = inst}
          />
        </Modal>
        {/* 根据字段名添加显示列及搜索条件 */}
        <Modal
          wrapClassName="common-table-fields-modal"
          title={this.state.dict['model.edit']}
          visible={this.state.tableVisible}
          width={'65vw'}
          maskClosable={false}
          cancelText={this.state.dict['model.close']}
          onOk={this.addFieldSubmit}
          onCancel={() => { // 取消添加
            this.setState({
              tableVisible: false
            })
          }}
          destroyOnClose
        >
          {this.state.fields && this.state.fields.length > 0 ?
            <EditCard data={this.state.fields} ref="searchcard" type={'form'} dict={this.state.dict} /> : null
          }
          {(!this.state.fields || this.state.fields.length === 0) &&
            <Empty />
          }
        </Modal>
        {/* 按钮使用系统存储过程时,验证信息模态框 */}
        <Modal
src/templates/formtabconfig/index.scss
@@ -59,41 +59,6 @@
      color: #1890ff;
      border-bottom: 1px solid #e8e8e8;
    }
    .tables {
      .ant-select-selection-selected-value {
        opacity: 0.4!important;
      }
    }
    .ant-list {
      .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;
        }
      }
    }
    .config-button {
      min-width: 65px;
    }
src/templates/modalconfig/index.scss
@@ -55,41 +55,6 @@
        }
      }
    }
    .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;
        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;
        }
      }
    }
  }
  .tools::-webkit-scrollbar {
    width: 4px;
src/templates/sharecomponent/tablecomponent/index.jsx
@@ -8,8 +8,6 @@
import Api from '@/api'
import options from '@/store/options.js'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import { queryTableSql } from '@/utils/option.js'
import './index.scss'
@@ -24,7 +22,6 @@
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    tables: [],          // 系统表
    tableFields: [],     // 已选表字段集
    selectedTables: [],  // 已选表
@@ -268,16 +265,16 @@
  render() {
    const { containerId } = this.props
    const { dict, tables, selectedTables } = this.state
    const { tables, selectedTables } = this.state
    return (
      <div className="model-table-tablemanage-view">
      <div className="model-tablename-manage-view">
        {/* 表名添加 */}
        <div className="ant-col ant-form-item-label">
          <label>
            <Tooltip placement="topLeft" title="此处可以添加页面配置相关的常用表,可通过工具栏中的添加按钮,可批量添加表格相关字段。">
              <QuestionCircleOutlined className="mk-form-tip" />
              {dict['header.menu.table.add']}
              表名
            </Tooltip>
          </label>
        </div>
@@ -307,7 +304,6 @@
          renderItem={(item, index) => <List.Item key={index} title={item.Remark + ' (' + item.TbName + ')'}>
            {item.Remark + ' (' + item.TbName + ')'}
            <CloseOutlined onClick={() => this.deleteTable(item)}/>
            <div className="bottom-mask"></div>
          </List.Item>}
        />}
      </div>
src/templates/sharecomponent/tablecomponent/index.scss
@@ -1,7 +1,33 @@
.model-table-tablemanage-view {
.model-tablename-manage-view {
  >.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;
      }
    }
  }
  .tables {
    width: 66.66666667%!important;
    .ant-select-selection-selected-value {
      opacity: 0.4!important;
    }
  }
  >.ant-form-item-label {
    width: 33.33333333%;
  }
}
src/templates/subtableconfig/index.scss
@@ -63,37 +63,6 @@
        border-bottom: 1px solid #e8e8e8;
      }
    }
    .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;
        }
      }
    }
    .config-button {
      min-width: 65px;
    }
src/templates/treepageconfig/index.scss
@@ -53,38 +53,6 @@
        }
      }
    }
    .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;
        }
      }
    }
    .config-button {
      min-width: 65px;
    }
src/views/imdesign/index.scss
@@ -61,36 +61,6 @@
          .ant-form-item {
            margin-bottom: 10px;
          }
          .model-table-tablemanage-view {
            >.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;
                }
              }
            }
            >.tables {
              width: 66.66666667%!important;
            }
            >.ant-form-item-label {
              width: 33.33333333%;
            }
          }
        }
      }
  
src/views/menudesign/index.scss
@@ -102,36 +102,6 @@
          .ant-form-item {
            margin-bottom: 10px;
          }
          .model-table-tablemanage-view {
            >.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;
                }
              }
            }
            >.tables {
              width: 66.66666667%!important;
            }
            >.ant-form-item-label {
              width: 33.33333333%;
            }
          }
        }
      }
src/views/mobdesign/index.scss
@@ -108,36 +108,6 @@
          .ant-form-item {
            margin-bottom: 10px;
          }
          .model-table-tablemanage-view {
            >.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;
                }
              }
            }
            >.tables {
              width: 66.66666667%!important;
            }
            >.ant-form-item-label {
              width: 33.33333333%;
            }
          }
        }
      }
  
@@ -308,7 +278,7 @@
          display: block;
        }
      }
      .url-field-component, .model-table-tablemanage-view, .ant-typography {
      .url-field-component, .model-tablename-manage-view, .ant-typography {
        display: none;
      }
    }
src/views/pcdesign/index.scss
@@ -113,36 +113,6 @@
          .ant-form-item {
            margin-bottom: 10px;
          }
          .model-table-tablemanage-view {
            >.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;
                }
              }
            }
            >.tables {
              width: 66.66666667%!important;
            }
            >.ant-form-item-label {
              width: 33.33333333%;
            }
          }
        }
      }
  
src/views/popdesign/index.scss
@@ -78,36 +78,6 @@
          .ant-form-item {
            margin-bottom: 10px;
          }
          .model-table-tablemanage-view {
            >.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;
                }
              }
            }
            >.tables {
              width: 66.66666667%!important;
            }
            >.ant-form-item-label {
              width: 33.33333333%;
            }
          }
        }
      }
src/views/tabledesign/index.jsx
@@ -22,7 +22,7 @@
const MenuForm = asyncComponent(() => import('./menuform'))
const Header = asyncComponent(() => import('@/menu/header'))
const MenuShell = asyncComponent(() => import('@/menu/menushell'))
const MenuShell = asyncComponent(() => import('@/menu/tableshell'))
const BgController = asyncComponent(() => import('@/pc/bgcontroller'))
const StyleController = asyncComponent(() => import('@/menu/stylecontroller'))
const ReplaceField = asyncComponent(() => import('@/menu/replaceField'))
@@ -43,7 +43,6 @@
    ParentId: '',
    MenuName: '',
    MenuNo: '',
    delButtons: [],
    activeKey: 'basedata',
    menuloading: false,
    oriConfig: null,
@@ -87,7 +86,6 @@
  }
  componentDidMount () {
    MKEmitter.addListener('delButtons', this.delButtons)
    MKEmitter.addListener('modalStatus', this.modalStatus)
    MKEmitter.addListener('changePopview', this.initPopview)
    MKEmitter.addListener('triggerMenuSave', this.triggerMenuSave)
@@ -156,7 +154,6 @@
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('delButtons', this.delButtons)
    MKEmitter.removeListener('modalStatus', this.modalStatus)
    MKEmitter.removeListener('changePopview', this.initPopview)
    MKEmitter.removeListener('triggerMenuSave', this.triggerMenuSave)
@@ -325,10 +322,6 @@
    })
  }
  delButtons = (items) => {
    this.setState({ delButtons: [...this.state.delButtons, ...items] })
  }
  initPopview = (card, btn) => {
    const { oriConfig, config } = this.state
@@ -401,7 +394,42 @@
            MenuName: MenuName,
            MenuNo: MenuNo,
            tables: [],
            components: [],
            components: [
              {
                uuid: Utils.getuuid(),
                type: 'table',
                tabId: '',
                parentId: '',
                format: 'array',    // 组件属性 - 数据格式
                pageable: true,     // 组件属性 - 是否可分页
                switchable: true,   // 组件属性 - 数据是否可切换
                dataName: '',
                width: 24,
                search: [
                  { origin: true, uuid: Utils.getuuid(), label: 'label', field: '', type: 'text', match: 'like' },
                  { origin: true, uuid: Utils.getuuid(), label: 'label', field: '', type: 'select', match: 'equal' }
                ],
                action: [
                  { origin: true, uuid: Utils.getuuid(), label: '添加', intertype: 'system', OpenType: 'pop', execSuccess: 'grid', icon: 'plus', class: 'green', style: {color: 'rgb(255, 255, 255)', background: 'rgb(38, 194, 129)', marginRight: '15px'} },
                  { origin: true, uuid: Utils.getuuid(), label: '修改', intertype: 'system', OpenType: 'pop', execSuccess: 'grid', icon: 'form', class: 'purple', style: {color: 'rgb(255, 255, 255)', background: 'rgb(142, 68, 173)', marginRight: '15px'} },
                  { origin: true, uuid: Utils.getuuid(), label: '删除', intertype: 'system', OpenType: 'prompt', execSuccess: 'grid', Ot: 'required', icon: 'delete', class: 'danger', style: {color: 'rgb(255, 255, 255)', background: 'rgb(255, 77, 79)', marginRight: '15px'} }
                ],
                name: '主表',
                subtype: 'basetable',
                setting: { interType: 'system' },
                wrap: {},
                style: {},
                headerStyle: {},
                columns: [],
                cols: [
                  { origin: true, uuid: Utils.getuuid(), Align: 'left', label: 'label1', field: '', Hide: 'false', type: 'text', Width: 120 },
                  { origin: true, uuid: Utils.getuuid(), Align: 'left', label: 'label2', field: '', Hide: 'false', IsSort: 'true', type: 'text', Width: 120 },
                  { origin: true, uuid: Utils.getuuid(), Align: 'left', label: 'label3', field: '', Hide: 'false', IsSort: 'true', type: 'text', Width: 120 },
                ],
                scripts: [],
                isNew: true
              }
            ],
            viewType: 'menu',
            style: {
              backgroundColor: '#ffffff', backgroundImage: '',
@@ -587,7 +615,7 @@
        LText: []
      }
      let delButtons = fromJS(this.state.delButtons).toJS()
      let delButtons = []
      btnParam.LText = this.getMenuMessage(delButtons)
      btnParam.LText = btnParam.LText.join(' union all ')
@@ -665,7 +693,6 @@
      }).then(res => {
        if (res && res.status) {
          this.setState({
            delButtons: [],
            menuloading: false
          })
          notification.success({
src/views/tabledesign/index.scss
@@ -64,7 +64,7 @@
      top: 48px;
      z-index: 10;
      height: calc(100vh - 48px);
      width: 300px;
      width: 280px;
      background: #ffffff;
      box-shadow: 0px 2px 5px #bcbcbc;
      transition: left 0.3s;
@@ -99,38 +99,9 @@
          color: #ffffff;
        }
        .ant-collapse-content-box {
          padding: 10px;
          .ant-form-item {
            margin-bottom: 10px;
          }
          .model-table-tablemanage-view {
            >.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;
                }
              }
            }
            >.tables {
              width: 66.66666667%!important;
            }
            >.ant-form-item-label {
              width: 33.33333333%;
            }
          }
        }
      }
@@ -175,8 +146,8 @@
    .menu-view {
      position: relative;
      width: calc(100vw - 300px);
      margin-left: 300px;
      width: calc(100vw - 280px);
      margin-left: 280px;
      height: calc(100vh - 50px);
      overflow-y: auto;
      transition: all 0.3s;
src/views/tabledesign/menuform/index.jsx
@@ -303,13 +303,6 @@
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label="助记码">
              {getFieldDecorator('easyCode', {
                initialValue: config.easyCode
              })(<Input placeholder="" autoComplete="off" onChange={this.changeEasyCode}/>)}
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label={'隐藏菜单'}>
              <Switch checkedChildren={'是'} checked={config.hidden === 'true'} unCheckedChildren={'否'} onChange={(value) => {
                this.selectChange('hidden', value + '')
@@ -317,6 +310,13 @@
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label="助记码">
              {getFieldDecorator('easyCode', {
                initialValue: config.easyCode
              })(<Input placeholder="" autoComplete="off" onChange={this.changeEasyCode}/>)}
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label="备注">
              {getFieldDecorator('Remark', {
                initialValue: config.Remark || '',