king
2021-10-08 24bba05db141f358bf1a8bb7213a2432c9de355e
2021-10-08
15个文件已修改
4个文件已添加
1907 ■■■■ 已修改文件
src/index.js 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/actionform/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/share/actioncomponent/formconfig.jsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/columns/editColumn/formconfig.jsx 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/columns/editColumn/index.jsx 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/columns/index.jsx 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/columns/index.scss 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/index.jsx 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/table/edit-table/options.jsx 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modulesource/option.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/group/normal-group/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/share/tabtransfer/index.jsx 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/index.jsx 508 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/index.scss 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/normalTable/index.jsx 553 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/edit-table/normalTable/index.scss 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/components/table/normal-table/index.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/actionform/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/index.js
@@ -110,9 +110,15 @@
      if (config.mainSystemApi) {
        let systemApi = config.mainSystemApi
        // 业务系统不允许连接云端,业务系统连接sso.mk9h.cn时,数据虚化处理
        if (systemApi && systemApi.indexOf('cloud.mk9h.cn') > -1) {
        if (systemApi.indexOf('cloud.mk9h.cn') > -1) {
          systemApi = ''
        } else if (systemApi && systemApi.indexOf('sso.mk9h.cn') > -1 && process.env.NODE_ENV === 'production') {
        } else if (/index.html/ig.test(systemApi)) {
          systemApi = systemApi.replace(/index.html.*/ig, 'webapi/dostars')
        } else if (!/webapi\/dostars$/ig.test(systemApi)) {
          systemApi = systemApi.replace(/\/?$/, '/webapi/dostars')
        }
        if (systemApi.indexOf('sso.mk9h.cn') > -1 && process.env.NODE_ENV === 'production') {
          GLOB.dataFormat = true
        }
      
src/menu/components/share/actioncomponent/actionform/index.jsx
@@ -124,7 +124,7 @@
          } else if (['innerpage', 'tab', 'popview', 'excelIn'].includes(_opentype)) {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl'].includes(op.value))
          } else if (card.sqlType === 'insert') {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl', 'required'].includes(op.value))
            item.options = this.state.requireOptions.filter(op => ['notRequired'].includes(op.value))
          } else {
            item.options = this.state.requireOptions
          }
@@ -328,7 +328,7 @@
          if (item.key === 'Ot' && type === 'card') {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl'].includes(op.value))
          } else if (item.key === 'Ot' && value === 'insert') {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl', 'required'].includes(op.value))
            item.options = this.state.requireOptions.filter(op => ['notRequired'].includes(op.value))
          } else if (item.key === 'Ot') {
            item.options = this.state.requireOptions
          }
src/menu/components/share/actioncomponent/formconfig.jsx
@@ -60,6 +60,18 @@
    })
  }
  if (type === 'editable') {
    opentypes = [
      {
        value: 'excelIn',
        text: Formdict['model.form.excelIn']
      }, {
        value: 'excelOut',
        text: Formdict['model.form.excelOut']
      }
    ]
  }
  let tabs = getTabs(JSON.parse(JSON.stringify(modules)))
  let pageTemps = [
src/menu/components/table/edit-table/columns/editColumn/formconfig.jsx
@@ -7,7 +7,7 @@
 * @description 获取显示列表单配置信息
 * @param {object} card       // 搜索条件对象
 */
export function getColumnForm (card, fields = []) {
export function getColumnForm (card, fields = [], columns = []) {
  let roleList = sessionStorage.getItem('sysRoles')
  if (roleList) {
    try {
@@ -42,6 +42,21 @@
    value: 'index',
    text: '序号'
  }]
  let editCols = [
    {
      field: '$next',
      label: '下一行'
    }
  ]
  columns.forEach(col => {
    if (col.editable === 'true' && col.uuid !== card.uuid) {
      editCols.push({
        field: col.uuid,
        label: col.label
      })
    }
  })
  return [
    {
@@ -125,6 +140,21 @@
    },
    {
      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: 'radio',
      key: 'editable',
      label: '可编辑',
      initVal: card.editable || 'false',
@@ -138,6 +168,52 @@
      }]
    },
    {
      type: 'radio',
      key: 'required',
      label: '必填',
      initVal: card.required || 'false',
      required: false,
      options: [{
        value: 'false',
        text: '否'
      }, {
        value: 'true',
        text: '是'
      }]
    },
    {
      type: 'text',
      key: 'initval',
      label: '默认值',
      initVal: card.initval,
      tooltip: '使用$copy时,表示新增时复制上一行信息。',
      required: false
    },
    {
      type: 'select',
      key: 'enter',
      label: '回车切换',
      initVal: card.enter || '$next',
      options: editCols
    },
    {
      type: 'radio',
      key: 'footEnter',
      label: '末行回车',
      initVal: card.footEnter || 'false',
      tooltip: '新增功能仅在表格可新增时有效。',
      options: [{
        value: 'sub',
        text: '提交'
      }, {
        value: 'add',
        text: '新增'
      }, {
        value: 'false',
        text: '无动作'
      }]
    },
    {
      type: 'number',
      key: 'decimal',
      min: 0,
@@ -148,6 +224,22 @@
      required: true
    },
    {
      type: 'number',
      key: 'max',
      label: '最大值',
      initVal: card.max,
      unlimit: true,
      required: false
    },
    {
      type: 'number',
      key: 'min',
      label: '最小值',
      initVal: card.min,
      unlimit: true,
      required: false
    },
    {
      type: 'select',
      key: 'format',
      label: Formdict['header.form.format'],
src/menu/components/table/edit-table/columns/editColumn/index.jsx
@@ -9,7 +9,7 @@
const columnTypeOptions = {
  text: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'prefix', 'postfix', 'textFormat', 'editable', 'blacklist'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'editable', 'blacklist'],
  number: ['label', 'field', 'type', 'Align', 'Hide', 'IsSort', 'Width', 'decimal', 'format', 'prefix', 'postfix', 'editable', 'sum', 'blacklist'],
  textarea: ['label', 'field', 'type', 'Align', 'Hide', 'Width', 'prefix', 'postfix', 'blacklist'],
  custom: ['label', 'type', 'Align', 'Hide', 'Width', 'blacklist'],
  action: ['label', 'type', 'Align', 'Width'],
@@ -21,6 +21,7 @@
    dict: PropTypes.object,     // 字典项
    visible: PropTypes.bool,
    column: PropTypes.object,
    columns: PropTypes.array,
    fields: PropTypes.array,
    submitCol: PropTypes.func,  // 提交事件
    cancelCol: PropTypes.func   // 取消时删除事件
@@ -38,8 +39,16 @@
  }
  editColumn = (column) => {
    let formlist = getColumnForm(column, this.props.fields)
    let formlist = getColumnForm(column, this.props.fields, this.props.columns)
    let _options = fromJS(columnTypeOptions[column.type]).toJS()
    if (column.editable === 'true') {
      if (column.type === 'text') {
        _options.push('required', 'initval', 'enter', 'footEnter')
      } else if (column.type === 'number') {
        _options.push('max', 'min', 'initval', 'enter', 'footEnter')
      }
    }
    this.setState({
      visible: true,
@@ -63,12 +72,24 @@
  }
  typeChange = (key, value, option) => {
    const { editable, type } = this.state
    if (key === 'type') {
      let _options = fromJS(columnTypeOptions[value]).toJS()
      if (editable === 'true') {
        if (value === 'text') {
          _options.push('required', 'initval', 'enter', 'footEnter')
        } else if (value === 'number') {
          _options.push('max', 'min', 'initval', 'enter', 'footEnter')
        }
      }
      this.setState({
        type: value,
        formlist: this.state.formlist.map(item => {
          if (item.key === 'editable') {
            item.initVal = editable
          }
          item.hidden = !_options.includes(item.key)
          return item
@@ -94,9 +115,20 @@
      if (values.type !== this.state.type) {
        let _options = fromJS(columnTypeOptions[values.type]).toJS()
        if (editable === 'true') {
          if (values.type === 'text') {
            _options.push('required', 'initval', 'enter', 'footEnter')
          } else if (values.type === 'number') {
            _options.push('max', 'min', 'initval', 'enter', 'footEnter')
          }
        }
        this.setState({
          type: values.type,
          formlist: this.state.formlist.map(item => {
            if (item.key === 'editable') {
              item.initVal = editable
            }
            item.hidden = !_options.includes(item.key)
            return item
@@ -110,7 +142,24 @@
    } else if (key === 'format' && value === 'percent') {
      this.props.form.setFieldsValue({postfix: '%'})
    } else if (key === 'editable') {
      let _options = fromJS(columnTypeOptions[type]).toJS()
      if (value === 'true') {
        if (type === 'text') {
          _options.push('required', 'initval', 'enter', 'footEnter')
        } else if (type === 'number') {
          _options.push('max', 'min', 'initval', 'enter', 'footEnter')
        }
      }
      this.setState({
        editable: value,
        formlist: this.state.formlist.map(item => {
          item.hidden = !_options.includes(item.key)
          return item
        })
      })
    }
  }
src/menu/components/table/edit-table/columns/index.jsx
@@ -51,7 +51,6 @@
  render() {
    const { connectDragSource, connectDropTarget, moveCol, addElement, updateCol, editColumn, 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={
@@ -69,26 +68,6 @@
          </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">
              {column && ['custom'].includes(column.type) ?
                <Icon className="plus" title="添加" type="plus" onClick={() => this.props.addElement(column)} /> : null
              }
              <Icon className="edit" title="编辑" type="edit" onClick={() => this.props.editColumn(column)} />
              <Icon className="close" title="删除" type="delete" onClick={this.deleteCol} />
              {column && ['text', 'number'].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>)
    }
  }
}
@@ -416,36 +395,6 @@
    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',
        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,
          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()
@@ -526,7 +475,32 @@
      }
    }
    const columns = this.handlecolumns(this.state.columns, fields, config)
    const columns = this.state.columns.map((col, index) => {
      return {
        title: col.label,
        dataIndex: col.uuid,
        align: col.Align,
        sorter: col.IsSort === 'true',
        onCell: () => ({
          column: col,
          width: col.Width,
          config: config,
          upComponent: this.updateCol
        }),
        onHeaderCell: () => ({
          index,
          column: col,
          fields: fields,
          align: col.Align,
          moveCol: this.moveCol,
          updateCol: this.updateCol,
          addElement: this.addElement,
          editColumn: this.editColumn,
          changeStyle: this.changeStyle,
          deleteCol: this.deleteCol,
        }),
      }
    })
    let style = {}
    if (config.wrap.color) {
@@ -537,7 +511,7 @@
    }
    return (
      <div className={`normal-table-columns ${config.setting.laypage} ${config.wrap.tableType} ${config.wrap.mode || ''}`} id={tableId}>
      <div className={`edit-table-columns ${config.setting.laypage} ${config.wrap.mode || ''}`} id={tableId}>
        <div className="col-control">
          <Icon title="复制显示列" type="copy" onClick={this.copycolumn} />
          <MarkColumn columns={fields} type="line" marks={lineMarks} onSubmit={this.updateLineMarks} />
@@ -553,7 +527,6 @@
            bordered={config.wrap.bordered !== 'false'}
            components={components}
            dataSource={this.state.data}
            rowSelection={config.wrap.tableType ? { type: 'radio' } : null}
            columns={columns}
            pagination={{
              current: 1,
@@ -565,7 +538,7 @@
            }}
          />
        </DndProvider>
        <EditColumn column={card} dict={dict} fields={fields} submitCol={this.submitCol} cancelCol={this.cancelCol}/>
        <EditColumn column={card} dict={dict} columns={this.state.columns} fields={fields} submitCol={this.submitCol} cancelCol={this.cancelCol}/>
      </div>
    )
  }
src/menu/components/table/edit-table/columns/index.scss
@@ -1,4 +1,4 @@
.normal-table-columns {
.edit-table-columns {
  position: relative;
  .ant-table {
    color: inherit;
@@ -80,24 +80,13 @@
    margin: 0;
  }
}
.normal-table-columns.false {
.edit-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 {
.edit-table-columns.ghost {
  .ant-table-thead > tr {
    > th {
      color: inherit;
src/menu/components/table/edit-table/index.jsx
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Popover, notification } from 'antd'
import { Icon, Popover } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import asyncIconComponent from '@/utils/asyncIconComponent'
@@ -22,7 +22,6 @@
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 {
@@ -51,15 +50,15 @@
        parentId: card.parentId || '',
        format: 'array',    // 组件属性 - 数据格式
        pageable: true,     // 组件属性 - 是否可分页
        switchable: true,   // 组件属性 - 数据是否可切换
        switchable: false,  // 组件属性 - 数据是否可切换
        dataName: card.dataName || '',
        width: card.width || 24,
        search: [],
        action: [],
        name: card.name,
        subtype: card.subtype,
        setting: { interType: 'system' },
        wrap: { name: card.name, width: card.width || 24, bordered: 'true', tableType: 'checkbox', show: 'true' },
        setting: { interType: 'system', laypage: 'false' },
        wrap: { name: card.name, width: card.width || 24, bordered: 'true', show: 'true' },
        style: { marginLeft: '8px', marginRight: '8px', marginTop: '8px', marginBottom: '8px' },
        headerStyle: { fontSize: '16px', borderBottomWidth: '1px', borderBottomColor: '#e8e8e8' },
        columns: [],
@@ -94,9 +93,7 @@
        })
        _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) {
          if (col.type === 'custom' && col.elements) {
            col.elements = col.elements.map(cell => {
              cell.uuid = Utils.getuuid()
              return cell
@@ -130,7 +127,6 @@
  componentDidMount () {
    MKEmitter.addListener('submitStyle', this.getStyle)
    MKEmitter.addListener('submitModal', this.handleSave)
    MKEmitter.addListener('logButton', this.logButton)
  }
  shouldComponentUpdate (nextProps, nextState) {
@@ -146,31 +142,11 @@
    }
    MKEmitter.removeListener('submitStyle', this.getStyle)
    MKEmitter.removeListener('submitModal', this.handleSave)
    MKEmitter.removeListener('logButton', this.logButton)
  }
  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
  }
  filterOrigin = (component) => {
    if (component.isNew) {
      let item = fromJS(component).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
@@ -193,20 +169,6 @@
    component.name = component.wrap.name
    this.filterOrigin(component)
  }
  logButton = (id, item) => {
    const { card } = this.state
    if (id !== card.uuid) return
    let btnlog = card.btnlog || []
    btnlog.push(item)
    this.setState({
      card: {...card, btnlog}
    })
    this.filterOrigin({...card, btnlog})
  }
  changeStyle = () => {
@@ -260,7 +222,7 @@
    newcard.label = 'label'
    newcard.sqlType = ''
    newcard.Ot = 'requiredSgl'
    newcard.OpenType = 'pop'
    newcard.OpenType = 'excelIn'
    newcard.icon = ''
    newcard.class = 'green'
    newcard.intertype = card.setting.interType || 'system'
@@ -328,46 +290,6 @@
    this.filterOrigin(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.setState({ card })
      this.filterOrigin(card)
      notification.success({
        top: 92,
        message: '恢复成功!',
        duration: 2
      })
    } else {
      card.btnlog = logs
      this.setState({ card })
      this.filterOrigin(card)
      notification.success({
        top: 92,
        message: '清除成功!',
        duration: 2
      })
    }
  }
  getWrapForms = () => {
    const { wrap, action } = this.state.card
@@ -403,7 +325,6 @@
            <CopyComponent type="normaltable" card={card}/>
            <PasteComponent config={card} options={['action', 'search', 'form', 'cols']} updateConfig={this.updateComponent} />
            <Icon className="style" title="调整样式" onClick={this.changeStyle} type="font-colors" />
            <LogComponent btnlog={card.btnlog || []} handlelog={this.handleLog} />
            <UserComponent config={card}/>
            <Icon className="close" title="删除组件" type="delete" onClick={() => this.props.deletecomponent(card.uuid)} />
            <SettingComponent config={card} updateConfig={this.updateComponent} />
@@ -412,7 +333,7 @@
          <Icon type="tool" />
        </Popover>
        <SearchComponent config={card} updatesearch={this.updateconfig}/>
        <ActionComponent config={card} setSubConfig={this.setSubConfig} updateaction={this.updateComponent}/>
        <ActionComponent type="editable" config={card} setSubConfig={this.setSubConfig} updateaction={this.updateComponent}/>
        <ColumnComponent config={card} updatecolumn={this.updateconfig}/>
      </div>
    )
src/menu/components/table/edit-table/options.jsx
@@ -33,21 +33,6 @@
    },
    {
      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',
@@ -95,18 +80,6 @@
    },
    {
      type: 'radio',
      field: 'selected',
      label: '首行选中',
      initval: wrap.selected || 'false',
      required: false,
      options: [
        {value: 'false', label: '无'},
        {value: 'init', label: '初始化'},
        {value: 'always', label: '数据加载'},
      ]
    },
    {
      type: 'radio',
      field: 'show',
      label: '搜索按钮',
      initval: wrap.show || 'true',
@@ -115,6 +88,17 @@
      options: [
        {value: 'true', label: '显示'},
        {value: 'false', label: '隐藏'},
      ]
    },
    {
      type: 'radio',
      field: 'addable',
      label: '可新增',
      initval: wrap.addable || 'false',
      required: false,
      options: [
        {value: 'true', label: '是'},
        {value: 'false', label: '否'},
      ]
    },
    {
@@ -163,8 +147,7 @@
      min: 10,
      max: 3000,
      precision: 0,
      required: false,
      forbid: appType === 'mob'
      required: false
    },
    {
      type: 'number',
@@ -175,19 +158,7 @@
      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'
      required: false
    },
    {
      type: 'multiselect',
src/menu/modulesource/option.jsx
@@ -36,7 +36,7 @@
  { type: 'menu', url: Carousel, component: 'carousel', subtype: 'datacard', title: '轮播-动态数据', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: Carousel1, component: 'carousel', subtype: 'propcard', title: '轮播-静态数据', width: 24, forbid: ['billPrint'] },
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'normaltable', title: '常用表', width: 24 },
  // { type: 'menu', url: NormalTable, component: 'table', subtype: 'editable', title: '表格(可编辑)', width: 24 },
  { type: 'menu', url: NormalTable, component: 'table', subtype: 'editable', title: '表格(可编辑)', width: 24 },
  { type: 'menu', url: TableCard, component: 'table', subtype: 'tablecard', title: '表格(卡片)', width: 12 },
  { type: 'menu', url: tree, component: 'tree', subtype: 'normaltree', title: '树形列表', width: 12 },
  { type: 'menu', url: line, component: 'line', subtype: 'line', title: '折线图', width: 24 },
src/tabviews/custom/components/group/normal-group/index.jsx
@@ -21,6 +21,7 @@
const DataCard = asyncComponent(() => import('@/tabviews/custom/components/card/data-card'))
const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
const NormalTable = asyncComponent(() => import('@/tabviews/custom/components/table/normal-table'))
const EditTable = asyncComponent(() => import('@/tabviews/custom/components/table/edit-table'))
const PropCard = asyncComponent(() => import('@/tabviews/custom/components/card/prop-card'))
const BraftEditor = asyncComponent(() => import('@/tabviews/custom/components/editor/braft-editor'))
const SandBox = asyncComponent(() => import('@/tabviews/custom/components/code/sand-box'))
@@ -213,6 +214,12 @@
            <NormalTable config={item} data={data} BID={_bid} BData={BData} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'table' && item.subtype === 'editable') {
        return (
          <Col span={item.width} key={item.uuid}>
            <EditTable config={item} data={data} BID={_bid} BData={BData} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'tree') {
        return (
          <Col span={item.width} key={item.uuid}>
src/tabviews/custom/components/share/tabtransfer/index.jsx
@@ -23,6 +23,7 @@
const AntvScatter = asyncComponent(() => import('@/tabviews/custom/components/chart/antv-scatter'))
const TableCard = asyncComponent(() => import('@/tabviews/custom/components/card/table-card'))
const NormalTable = asyncComponent(() => import('@/tabviews/custom/components/table/normal-table'))
const EditTable = asyncComponent(() => import('@/tabviews/custom/components/table/edit-table'))
const PropCard = asyncComponent(() => import('@/tabviews/custom/components/card/prop-card'))
const NormalGroup = asyncComponent(() => import('@/tabviews/custom/components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('@/tabviews/custom/components/editor/braft-editor'))
@@ -235,6 +236,12 @@
            <NormalTable config={item} data={data} BID={BID} BData={BData} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'table' && item.subtype === 'editable') {
        return (
          <Col span={item.width} key={item.uuid}>
            <EditTable config={item} data={data} BID={BID} BData={BData} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'group' && item.subtype === 'normalgroup') {
        return (
          <Col span={item.width} key={item.uuid}>
src/tabviews/custom/components/table/edit-table/index.jsx
New file
@@ -0,0 +1,508 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { notification } from 'antd'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import UtilsDM from '@/utils/utils-datamanage.js'
import asyncComponent from '@/utils/asyncComponent'
import MKEmitter from '@/utils/events.js'
import './index.scss'
// 通用组件
const MainSearch = asyncComponent(() => import('@/tabviews/zshare/topSearch'))
const MainAction = asyncComponent(() => import('@/tabviews/zshare/actionList'))
const MainTable = asyncComponent(() => import('./normalTable'))
const NormalHeader = asyncComponent(() => import('@/tabviews/custom/components/share/normalheader'))
class EditableTable extends Component {
  static propTpyes = {
    BID: PropTypes.any,              // 父级Id
    data: PropTypes.array,           // 统一查询数据
    config: PropTypes.object,        // 组件配置信息
    mainSearch: PropTypes.any,       // 外层搜索条件
    menuType: PropTypes.any,         // 菜单类型
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    BID: '',              // 上级ID
    BData: '',            // 上级组件行数据
    config: {},           // 页面配置信息,包括按钮、搜索、显示列、标签等
    searchlist: null,     // 搜索条件
    actions: null,        // 按钮集
    columns: null,        // 显示列
    arr_field: '',        // 使用 sPC_Get_TableData 时的查询字段集
    setting: null,        // 页面全局设置:数据源、按钮及显示列固定、主键等
    data: [],             // 列表数据集
    total: 0,             // 总数
    loading: false,       // 列表数据加载中
    pageIndex: 1,         // 页码
    pageSize: 10,         // 每页数据条数
    orderBy: '',          // 排序
    search: '',           // 搜索条件数组,使用时需分场景处理
    statFValue: []        // 合计值
  }
  /**
   * @description 初始化处理
   * 1、 initdata 为打印时使用的数据集
   */
  UNSAFE_componentWillMount () {
    const { data, initdata, BID, BData } = this.props
    let _config = fromJS(this.props.config).toJS()
    let _cols = new Map()
    let _data = null
    let _sync = _config.setting.sync === 'true'
    let setting = {..._config.setting, ..._config.wrap, style: {}}
    if (_config.setting.sync === 'true' && data) {
      _data = data[_config.dataName] || []
      _sync = false
    } else if (_config.setting.sync === 'true' && initdata) {
      _data = initdata || []
      _sync = false
    }
    if (_data) {
      _data = _data.map((item, index) => {
        item.key = index
        item.$$uuid = item[_config.setting.primaryKey] || ''
        item.$$BID = BID || ''
        item.$$BData = BData || ''
        item.$Index = index + 1 + ''
        return item
      })
    }
    _config.columns.forEach(item => {
      _cols.set(item.field, item)
    })
    _config.cols.forEach(column => {
      if (column.type === 'custom') {
        column.elements = column.elements.map(item => {
          if (item.field && _cols.has(item.field)) {
            item.col = _cols.get(item.field)
          }
          return item
        })
      } else if (column.type === 'action') {
        column.operations = column.elements
      }
    })
    if (setting.color) {
      setting.style.color = setting.color
    }
    if (setting.fontSize) {
      setting.style.fontSize = setting.fontSize
    }
    if (!_config.lineMarks || _config.lineMarks.length === 0) {
      _config.lineMarks = null
    }
    this.setState({
      BID: BID || '',
      BData: BData || '',
      title: _config.wrap.title,
      sync: _sync,
      data: _data,
      config: _config,
      setting: setting,
      searchlist: _config.search,
      actions: _config.action,
      columns: _config.cols,
      arr_field: _config.columns.map(col => col.field).join(','),
      search: Utils.initMainSearch(_config.search) // 搜索条件初始化(含有时间格式,需要转化)
    }, () => {
      if (_config.setting.sync !== 'true' && _config.setting.onload === 'true') {
        this.loadmaindata()
        this.getStatFieldsValue()
      } else if (_config.setting.onload === 'true') {
        this.getStatFieldsValue()
      }
    })
  }
  /**
   * @description 主表数据加载
   * @param { Boolean } reset  表格是否重置
   * @param { String }  repage 表格是否重置页码
   */
  async loadmaindata (reset, repage) {
    const { mainSearch } = this.props
    const { setting, config, arr_field, search, orderBy, BID, pageIndex, pageSize, BData } = this.state
    if (setting.supModule && !BID) { // BID 不存在时,不做查询
      this.setState({
        data: [],
        total: 0
      })
      reset && MKEmitter.emit('resetTable', config.uuid, repage) // 列表重置
      return
    }
    let searches = fromJS(search).toJS()
    if (config.setting.useMSearch && mainSearch && mainSearch.length > 0) { // 主表搜索条件
      let keys = searches.map(item => item.key.toLowerCase())
      mainSearch.forEach(item => {
        if (!keys.includes(item.key.toLowerCase())) {
          searches.push(item)
        }
      })
    }
    let requireFields = searches.filter(item => item.required && item.value === '')
    if (requireFields.length > 0) {
      return
    }
    this.setState({
      loading: true
    })
    let _orderBy = orderBy || setting.order
    let param = UtilsDM.getQueryDataParams(setting, arr_field, searches, _orderBy, pageIndex, pageSize, BID, this.props.menuType)
    let result = await Api.genericInterface(param)
    if (result.status) {
      reset && MKEmitter.emit('resetTable', config.uuid, repage) // 列表重置
      let start = 1
      if (setting.laypage) {
        start = pageSize * (pageIndex - 1) + 1
      }
      this.setState({
        data: result.data.map((item, index) => {
          item.key = index
          item.$$uuid = item[setting.primaryKey] || ''
          item.$$BID = BID || ''
          item.$$BData = BData || ''
          item.$Index = start + index + ''
          return item
        }),
        total: result.total,
        loading: false
      })
    } else {
      this.setState({
        loading: false
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  /**
   * @description 获取合计字段值
   */
  getStatFieldsValue = () => {
    const { mainSearch } = this.props
    const { setting, config, search, BID, orderBy } = this.state
    if (setting.supModule && !BID) { // BID 不存在时,不做查询
      this.setState({
        statFValue: []
      })
      return
    }
    if (config.statFields.length === 0 || setting.interType !== 'system' || !setting.dataresource) return
    let searches = fromJS(search).toJS()
    if (config.setting.useMSearch && mainSearch && mainSearch.length > 0) { // 主表搜索条件
      let keys = searches.map(item => item.key.toLowerCase())
      mainSearch.forEach(item => {
        if (!keys.includes(item.key.toLowerCase())) {
          searches.push(item)
        }
      })
    }
    let requireFields = searches.filter(item => item.required && item.value === '')
    if (requireFields.length > 0) {
      return
    }
    let _orderBy = orderBy || setting.order
    let param = UtilsDM.getStatQueryDataParams(setting, config.statFields, searches, _orderBy, BID, this.props.menuType)
    Api.genericInterface(param).then(res => {
      if (res.status) {
        let _data = res.data[0]
        let values = []
        if (_data) {
          config.statFields.forEach(item => {
            if (_data[item.field] || _data[item.field] === 0) {
              let val = +_data[item.field]
              if (isNaN(val)) {
                val = 0
              }
              val = val.toFixed(item.decimal)
              values.push({label: item.label, value: val})
            }
          })
        }
        this.setState({
          statFValue: values
        })
      } else {
        this.setState({
          statFValue: []
        })
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    })
  }
  /**
   * @description 搜索条件改变时,重置表格数据
   * 含有初始不加载的页面,修改设置
   */
  refreshbysearch = (searches) => {
    const { setting } = this.state
    if (setting.onload === 'false') {
      this.setState({
        pageIndex: 1,
        search: searches,
        setting: {...setting, onload: 'true'}
      }, () => {
        this.loadmaindata()
        this.getStatFieldsValue()
      })
    } else {
      this.setState({
        pageIndex: 1,
        search: searches
      }, () => {
        this.loadmaindata(true, 'true')
        this.getStatFieldsValue()
      })
    }
  }
  /**
   * @description 表格条件改变时重置数据(分页或排序)
   */
  refreshbytable = (pagination, filters, sorter) => {
    if (sorter.order) {
      let _chg = {
        ascend: 'asc',
        descend: 'desc'
      }
      sorter.order = _chg[sorter.order]
    }
    this.setState({
      pageIndex: pagination.current,
      pageSize: pagination.pageSize,
      orderBy: (sorter.field && sorter.order) ? `${sorter.field} ${sorter.order}` : ''
    }, () => {
      this.loadmaindata()
    })
  }
  /**
   * @description 表格刷新
   */
  reloadtable = (btn) => {
    if (!btn || btn.resetPageIndex !== 'false') {
      this.setState({
        pageIndex: 1
      }, () => {
        this.loadmaindata(true, 'true')
        this.getStatFieldsValue()
      })
    } else {
      this.loadmaindata(true, 'false')
      this.getStatFieldsValue()
    }
  }
  /**
   * @description 导出Excel时,获取页面搜索排序等参数
   */
  queryModuleParam = (menuId, btnId) => {
    const { mainSearch } = this.props
    const { arr_field, config, orderBy, search, setting} = this.state
    if (config.uuid !== menuId) return
    let searches = search ? fromJS(search).toJS() : []
    if (config.setting.useMSearch && mainSearch && mainSearch.length > 0) { // 主表搜索条件
      let keys = searches.map(item => item.key.toLowerCase())
      mainSearch.forEach(item => {
        if (!keys.includes(item.key.toLowerCase())) {
          searches.push(item)
        }
      })
    }
    MKEmitter.emit('returnModuleParam', config.uuid, btnId, {
      arr_field: arr_field,
      orderBy: orderBy || setting.order,
      search: searches,
      menuName: config.name
    })
  }
  reloadData = (menuId, id) => {
    const { config } = this.state
    if (config.uuid !== menuId) return
    this.reloadtable()
  }
  resetParentParam = (MenuID, id, data) => {
    const { setting } = this.state
    if (!setting.supModule || setting.supModule !== MenuID) return
    if (id !== this.state.BID) {
      this.setState({
        pageIndex: 1,
        BID: id,
        BData: data
      }, () => {
        this.loadmaindata(true, 'true')
        this.getStatFieldsValue()
      })
    }
  }
  /**
   * @description 按钮执行完成后页面刷新
   * @param {*} menuId     // 菜单Id
   * @param {*} position   // 刷新位置
   * @param {*} btn        // 执行的按钮
   */
  refreshByButtonResult = (menuId, position, btn) => {
    const { config, BID } = this.state
    if (config.uuid !== menuId) return
    this.reloadtable(btn)                                                      // 数据刷新
    if (btn.syncComponentId && btn.syncComponentId !== config.uuid && btn.syncComponentId !== config.setting.supModule) {
      MKEmitter.emit('reloadData', btn.syncComponentId)                        // 同级标签刷新
    }
    if (position === 'mainline' && config.setting.supModule) {                 // 主表行刷新
      MKEmitter.emit('reloadData', config.setting.supModule, (BID || 'empty'))
    } else if (position === 'popclose') {                                      // 标签关闭刷新
      config.setting.supModule && MKEmitter.emit('reloadData', config.setting.supModule, (BID || 'empty'))
      btn.$tabId && MKEmitter.emit('refreshPopButton', btn.$tabId)
    }
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    const { sync, config, BID, BData } = this.state
    if (sync && !is(fromJS(this.props.data), fromJS(nextProps.data))) {
      let _data = []
      if (nextProps.data && nextProps.data[config.dataName]) {
        _data = nextProps.data[config.dataName] || []
        _data = _data.map((item, index) => {
          item.key = index
          item.$$uuid = item[config.setting.primaryKey] || ''
          item.$$BID = BID || ''
          item.$$BData = BData || ''
          item.$Index = index + 1 + ''
          return item
        })
      }
      this.setState({sync: false, data: _data})
    } else if (config.setting.syncRefresh && nextProps.mainSearch && !is(fromJS(this.props.mainSearch), fromJS(nextProps.mainSearch))) {
      this.setState({pageIndex: 1}, () => {
        this.reloadtable()
      })
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  componentDidMount () {
    MKEmitter.addListener('reloadData', this.reloadData)
    MKEmitter.addListener('resetSelectLine', this.resetParentParam)
    MKEmitter.addListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.addListener('refreshByButtonResult', this.refreshByButtonResult)
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('reloadData', this.reloadData)
    MKEmitter.removeListener('resetSelectLine', this.resetParentParam)
    MKEmitter.removeListener('queryModuleParam', this.queryModuleParam)
    MKEmitter.removeListener('refreshByButtonResult', this.refreshByButtonResult)
  }
  render() {
    const { BID, setting, searchlist, actions, config, columns, BData } = this.state
    return (
      <div className="custom-edit-table" style={config.style}>
        <NormalHeader config={config}/>
        {searchlist && searchlist.length ?
          <MainSearch BID={BID} setting={config.wrap} searchlist={searchlist} menuType={this.props.menuType} refreshdata={this.refreshbysearch}/> : null
        }
        <MainAction
          BID={BID}
          setting={setting}
          actions={actions}
          BData={BData}
          columns={config.columns}
          selectedData={[]}
        />
        <div className={'main-table-box ' + (!actions || actions.length === 0 ? 'no-action' : '')}>
          <MainTable
            setting={setting}
            columns={columns}
            MenuID={config.uuid}
            data={this.state.data}
            fields={config.columns}
            total={this.state.total}
            lineMarks={config.lineMarks}
            loading={this.state.loading}
            refreshdata={this.refreshbytable}
            statFValue={this.state.statFValue}
          />
        </div>
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    menuType: state.editLevel
  }
}
const mapDispatchToProps = () => {
  return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(EditableTable)
src/tabviews/custom/components/table/edit-table/index.scss
New file
@@ -0,0 +1,96 @@
.custom-edit-table {
  position: relative;
  background-color: #fff;
  .normal-header {
    margin-bottom: 10px;
  }
  .top-search {
    border-bottom: 1px solid #efefef;
  }
  >.button-list.toolbar-button {
    padding: 0;
    line-height: 45px;
    padding-right: 60px;
    button {
      margin-right: 0px;
      margin-bottom: 0px;
      min-height: 28px;
      height: auto;
    }
  }
  .ant-modal-mask {
    position: absolute;
  }
  .ant-modal-wrap {
    position: absolute;
  }
  .action-modal .ant-modal {
    top: 40px;
    max-width: 95%;
    .ant-modal-body {
      max-height: calc(100vh - 265px);
    }
  }
  .main-table-box {
    position: relative;
    min-height: 150px;
    .main-pickup {
      position: absolute;
      right: 5px;
      top: -22px;
      z-index: 2;
    }
    .custom-control {
      position: absolute;
      z-index: 1;
      right: 0px;
      top: -23px;
      font-size: 18px;
      padding: 3px;
      cursor: pointer;
    }
    >.async-spin {
      line-height: 150px!important;
    }
  }
  .no-action.main-table-box {
    .main-pickup {
      position: relative;
      right: 0px;
      top: 0px;
      z-index: 2;
      float: right;
    }
  }
  .ant-collapse {
    background-color: transparent;
    border-radius: 0px;
    > .ant-collapse-item {
      border: 0;
      >.ant-collapse-header {
        padding: 0;
        .normal-header {
          padding-right: 40px;
        }
      }
    }
    .ant-collapse-item:last-child > .ant-collapse-content {
      border-radius: 0;
      .ant-collapse-content-box {
        padding: 0;
        >.button-list.toolbar-button {
          padding: 0;
          line-height: 45px;
          padding-right: 60px;
          button {
            margin-right: 0px;
            margin-bottom: 0px;
            min-height: 28px;
            height: auto;
          }
        }
      }
    }
  }
}
src/tabviews/custom/components/table/edit-table/normalTable/index.jsx
New file
@@ -0,0 +1,553 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Table, Typography, Icon, Switch, Input, InputNumber } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import MKEmitter from '@/utils/events.js'
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import '@/assets/css/table.scss'
import './index.scss'
const { Paragraph } = Typography
const CardCellComponent = asyncComponent(() => import('@/tabviews/custom/components/card/cardcellList'))
class BodyRow extends React.Component {
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props.data), fromJS(nextProps.data))
  }
  render() {
    let { lineMarks, data, ...resProps } = this.props
    let style = {}
    let className = ''
    lineMarks && lineMarks.some(mark => {
      let originVal = data[mark.field[0]] + ''
      let contrastVal = ''
      let result = false
      if (mark.field[1] === 'static') {
        contrastVal = mark.contrastValue + ''
      } else {
        contrastVal = data[mark.field[2]] + ''
      }
      if (mark.match === '=') {
        result = originVal === contrastVal
      } else if (mark.match === '!=') {
        result = originVal !== contrastVal
      } else if (mark.match === 'like') {
        result = originVal.indexOf(contrastVal) > -1
      } else if (mark.match === '>') {
        try {
          originVal = parseFloat(originVal)
          contrastVal = parseFloat(contrastVal)
        } catch (e) {
          originVal = NaN
        }
        if (!isNaN(originVal) && !isNaN(contrastVal) && originVal > contrastVal) {
          result = true
        }
      } else if (mark.match === '<') {
        try {
          originVal = parseFloat(originVal)
          contrastVal = parseFloat(contrastVal)
        } catch (e) {
          originVal = NaN
        }
        if (!isNaN(originVal) && !isNaN(contrastVal) && originVal < contrastVal) {
          result = true
        }
      }
      if (result) {
        if (mark.signType[0] === 'font') {
          style.color = mark.color
        } else if (mark.signType[0] === 'background') {
          style.background = mark.color
          if (mark.fontColor) {
            style.color = mark.fontColor
          }
          className = 'background'
        } else if (mark.signType[0] === 'underline') {
          style.textDecoration = 'underline'
          style.color = mark.color
        } else if (mark.signType[0] === 'line-through') {
          style.textDecoration = 'line-through'
          style.color = mark.color
        }
      }
      return result
    })
    return <tr {...resProps} className={className} style={style}/>
  }
}
class BodyCell extends React.Component {
  state = {
    editing: false
  }
  getMark = (record, marks, style, content) => {
    marks.some(mark => {
      let originVal = record[mark.field[0]] + ''
      let contrastVal = ''
      let result = false
      if (mark.field[1] === 'static') {
        contrastVal = mark.contrastValue + ''
      } else {
        contrastVal = record[mark.field[2]] + ''
      }
      if (mark.match === '=') {
        result = originVal === contrastVal
      } else if (mark.match === '!=') {
        result = originVal !== contrastVal
      } else if (mark.match === 'like') {
        result = originVal.indexOf(contrastVal) > -1
      } else if (mark.match === '>') {
        try {
          originVal = parseFloat(originVal)
          contrastVal = parseFloat(contrastVal)
        } catch (e) {
          originVal = NaN
        }
        if (!isNaN(originVal) && !isNaN(contrastVal) && originVal > contrastVal) {
          result = true
        }
      } else if (mark.match === '<') {
        try {
          originVal = parseFloat(originVal)
          contrastVal = parseFloat(contrastVal)
        } catch (e) {
          originVal = NaN
        }
        if (!isNaN(originVal) && !isNaN(contrastVal) && originVal < contrastVal) {
          result = true
        }
      }
      if (result) {
        if (mark.signType[0] === 'font') {
          style.color = mark.color
        } else if (mark.signType[0] === 'background') {
          style.background = mark.color
          if (mark.fontColor) {
            style.color = mark.fontColor
          }
        } else if (mark.signType[0] === 'underline') {
          style.textDecoration = 'underline'
          style.color = mark.color
        } else if (mark.signType[0] === 'line-through') {
          style.textDecoration = 'line-through'
          style.color = mark.color
        } else if (mark.signType[0] === 'icon') {
          let icon = (<Icon style={{color: mark.color}} type={mark.signType[3]} />)
          if (mark.signType[1] === 'front') {
            content = <span>{icon} {content}</span>
          } else {
            content = <span>{content} {icon}</span>
          }
        }
      }
      return result
    })
    return content
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props.record), fromJS(nextProps.record)) || nextState.editing !== this.state.editing
  }
  componentDidMount () {
    MKEmitter.addListener('tdFocus', this.tdFocus)
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('tdFocus', this.tdFocus)
  }
  tdFocus = (id) => {
    const { col, record } = this.props
    if (id !== col.uuid + record.$Index) return
    this.focus()
  }
  enterPress = () => {
    const { col, record } = this.props
    this.setState({editing: false})
    if (col.enter === '$next') {
      MKEmitter.emit('nextLine', col, record.$Index)
    } else {
      MKEmitter.emit('tdFocus', col.enter + record.$Index)
    }
  }
  focus = () => {
    const { col, record } = this.props
    this.setState({editing: true, value: record[col.field] !== undefined ? record[col.field] : ''}, () => {
      let node = document.getElementById(col.uuid + record.$Index)
      node && node.select()
    })
  }
  onBlur = () => {
    this.setState({editing: false})
  }
  render() {
    let { col, config, record, style, className } = this.props
    const { editing, value } = this.state
    let children = null
    if (col.type === 'text') {
      let content = ''
      if (record[col.field] !== undefined) {
        content = `${record[col.field]}`
      }
      if (content !== '') {
        if (col.textFormat === 'YYYY-MM-DD' && /^[1-9]\d{3}(-|\/)(0[1-9]|1[0-2])(-|\/)(0[1-9]|[1-2][0-9]|3[0-1])/.test(content)) {
          content = `${content.substr(0, 4)}-${content.substr(5, 2)}-${content.substr(8, 2)}`
        } else if (col.textFormat === 'YYYY-MM-DD HH:mm:ss' && /^[1-9]\d{3}(-|\/)(0[1-9]|1[0-2])(-|\/)(0[1-9]|[1-2][0-9]|3[0-1]).([0-1][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]/.test(content)) {
          content = `${content.substr(0, 4)}-${content.substr(5, 2)}-${content.substr(8, 2)} ${content.substr(11, 2)}:${content.substr(14, 2)}:${content.substr(17, 2)}`
        }
        content = (col.prefix || '') + content + (col.postfix || '')
      }
      if (col.marks) {
        style = style || {}
        content = this.getMark(record, col.marks, style, content)
      }
      if (col.editable === 'true') {
        if (editing) {
          return (<td className="editing_table_cell">
            <Input id={col.uuid + record.$Index} defaultValue={value} onChange={(e) => this.setState({value: e.target.value})} onPressEnter={this.enterPress} onBlur={this.onBlur}/>
          </td>)
        } else {
          return (<td className={className + ' pointer'} style={style}><div className="mk-mask" onClick={this.focus}></div>{content}</td>)
        }
      } else {
        children = content
      }
    } else if (col.type === 'number') {
      let content = ''
      try {
        content = parseFloat(record[col.field])
        if (isNaN(content)) {
          content = ''
        }
      } catch (e) {
        content = ''
      }
      if (content !== '') {
        let decimal = col.decimal || 0
        if (col.format === 'percent') {
          content = content * 100
          decimal = decimal > 2 ? decimal - 2 : 0
        }
        content = content.toFixed(decimal)
        if (col.format === 'thdSeparator') {
          content = content.replace(/\d{1,3}(?=(\d{3})+(\.\d*)?$)/g, '$&,')
        }
        content = col.prefix + content + col.postfix
      }
      if (col.marks) {
        style = style || {}
        content = this.getMark(record, col.marks, style, content)
      }
      if (col.editable === 'true') {
        if (editing) {
          return (<td className="editing_table_cell">
            <InputNumber id={col.uuid + record.$Index} defaultValue={value} onChange={(val) => this.setState({value: val})} onPressEnter={this.enterPress} onBlur={this.onBlur}/>
          </td>)
        } else {
          return (<td className={className + ' pointer'} style={style}><div className="mk-mask" onClick={this.focus}></div>{content}</td>)
        }
      } else {
        children = content
      }
    } else if (col.type === 'textarea') {
      let content = ''
      if (record[col.field] !== undefined) {
        content = `${record[col.field]}`
      }
      if (content) {
        content = col.prefix + content + col.postfix
      }
      children = (
        <div>
          {content ? <Paragraph copyable ellipsis={{ rows: 3, expandable: true }}>{content}</Paragraph> : null }
        </div>
      )
    } else if (col.type === 'custom') {
      style.padding = '0px'
      if (col.style) {
        style = {...style, ...col.style}
      }
      children = (
        <CardCellComponent data={record} cards={config} elements={col.elements}/>
      )
    } else if (col.type === 'action') {
      style.padding = '0px 5px'
      children = (
        <CardCellComponent data={record} cards={config} elements={col.elements}/>
      )
    }
    return (<td className={className} style={style}>{children}</td>)
  }
}
class NormalTable extends Component {
  static propTpyes = {
    statFValue: PropTypes.any,       // 合计字段数据
    MenuID: PropTypes.string,        // 菜单Id
    setting: PropTypes.object,       // 表格全局设置:tableType(表格是否可选、单选、多选)、columnfixed(列固定)、actionfixed(按钮固定)
    columns: PropTypes.array,        // 表格列
    lineMarks: PropTypes.any,        // 行标记
    fields: PropTypes.array,         // 组件字段集
    BData: PropTypes.any,            // 主表数据
    data: PropTypes.any,             // 表格数据
    total: PropTypes.any,            // 总数
    loading: PropTypes.bool,         // 表格加载中
    refreshdata: PropTypes.func,     // 表格中排序列、页码的变化时刷新
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    data: [],
    tableId: '',          // 表格ID
    pageIndex: 1,         // 初始页面索引
    pageSize: 10,         // 每页数据条数
    columns: null,        // 显示列
    pickup: false,        // 收起未选择项
    orderfields: {}       // 排序id与field转换
  }
  UNSAFE_componentWillMount () {
    const { setting, fields, columns } = this.props
    let orderfields = {}
    let initEditLine = null
    let _columns = columns.map(item => {
      if (item.type === 'index') {
        item.field = '$Index'
        item.type = 'text'
      }
      if (!initEditLine && item.editable === 'true') {
        initEditLine = item
      }
      if (item.marks && item.marks.length === 0) {
        item.marks = ''
      }
      if (item.field) {
        orderfields[item.uuid] = item.field
      }
      return {
        align: item.Align,
        dataIndex: item.uuid,
        title: item.label,
        sorter: item.field && item.IsSort === 'true',
        width: item.Width || 120,
        onCell: record => ({
          record,
          col: item,
          config: item.type === 'custom' || item.type === 'action' ? {setting, columns: fields} : null,
        })
      }
    })
    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('')
    }) ()
    if (setting.borderColor) { // 边框颜色
      let style = `#${tableId} table, #${tableId} tr, #${tableId} th, #${tableId} td {border-color: ${setting.borderColor}}`
      let ele = document.createElement('style')
      ele.innerHTML = style
      document.getElementsByTagName('head')[0].appendChild(ele)
    }
    this.setState({
      columns: _columns,
      tableId,
      orderfields,
      initEditLine
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  componentDidMount () {
    MKEmitter.addListener('resetTable', this.resetTable)
    MKEmitter.addListener('nextLine', this.nextLine)
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('resetTable', this.resetTable)
    MKEmitter.removeListener('nextLine', this.nextLine)
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!is(fromJS(this.props.data), fromJS(nextProps.data))) {
      this.setState({data: nextProps.data || []})
    }
  }
  nextLine = (col, index) => {
    const { data, initEditLine } = this.state
    index = +index
    if (index < data.length && initEditLine) {
      MKEmitter.emit('tdFocus', initEditLine.uuid + (index + 1))
    }
  }
  changeTable = (pagination, filters, sorter) => {
    const { orderfields } = this.state
    this.setState({
      pageIndex: pagination.current,
      pageSize: pagination.pageSize,
      pickup: false
    })
    sorter.field = orderfields[sorter.field] || ''
    this.props.refreshdata(pagination, filters, sorter)
  }
  resetTable = (id, repage) => {
    const { MenuID } = this.props
    if (id !== MenuID) return
    if (repage === 'false') {
      this.setState({
        pickup: false
      })
    } else {
      this.setState({
        pageIndex: 1,
        pickup: false
      })
    }
  }
  pickupChange = () => {
    this.setState({
      pickup: !this.state.pickup
    })
  }
  render() {
    const { setting, statFValue, lineMarks } = this.props
    const { pickup, tableId, data } = this.state
    const components = {
      body: {
        row: BodyRow,
        cell: BodyCell
      }
    }
    // 数据收起时,过滤已选数据
    let _data = data || []
    if (pickup) {
      _data = _data.filter(item => !item.$deleted)
    }
    let _pagination = false
    if (setting.laypage !== 'false' && setting.laypage !== false) {
      _pagination = {
        current: this.state.pageIndex,
        pageSize: this.state.pageSize,
        pageSizeOptions: ['10', '25', '50', '100', '500', '1000'],
        showSizeChanger: true,
        total: this.props.total || 0,
        showTotal: (total, range) => `${range[0]}-${range[1]} ${this.state.dict['main.pagination.of']} ${total} ${this.state.dict['main.pagination.items']}`
      }
    }
    let _footer = ''
    if (statFValue && statFValue.length > 0) {
      _footer = statFValue.map(f => `${f.label}(合计):${f.value}`).join(';')
    }
    let height = setting.height || false
    return (
      <div className={`edit-custom-table ${pickup ? 'editable' : ''} ${setting.tableHeader || ''} ${height ? 'fixed-height' : ''} ${setting.mode || ''}`} id={tableId}>
        <Switch title="编辑" className="main-pickup" checkedChildren="开" unCheckedChildren="关" checked={pickup} onChange={this.pickupChange} />
        <Table
          components={components}
          style={setting.style}
          size={setting.size || 'middle'}
          bordered={setting.bordered !== 'false'}
          columns={this.state.columns}
          dataSource={_data}
          loading={this.props.loading}
          scroll={{ x: '100%', y: height }}
          onRow={(record, index) => {
            return {
              lineMarks,
              data: record
            }
          }}
          onChange={this.changeTable}
          pagination={_pagination}
        />
        {_footer ? <div className={'normal-table-footer ' + (_pagination ? 'pagination' : '')}>{_footer}</div> : null}
      </div>
    )
  }
}
export default NormalTable
src/tabviews/custom/components/table/edit-table/normalTable/index.scss
New file
@@ -0,0 +1,294 @@
.edit-custom-table {
  position: relative;
  padding: 0px;
  .normal-table-footer {
    padding: 10px 0px;
    color: rgba(0, 0, 0, 0.65);
  }
  .normal-table-footer.pagination {
    position: absolute;
    bottom: 10px;
  }
  >.ant-table-wrapper {
    position: relative;
    z-index: 1;
  }
  .ant-table {
    color: inherit;
    font-size: inherit;
  }
  table {
    max-width: 100%;
    width: 100%;
    .ant-table-thead {
      tr {
        th {
          position: relative;
        }
      }
    }
    .ant-table-selection-column {
      width: 60px;
      min-width: 60px;
      max-width: 60px;
    }
    .ant-table-tbody > tr.ant-table-row-selected:not(.background) td {
      background-color: #c4ebfd;
    }
    .ant-table-tbody > tr.ant-table-row-selected:not(.background):hover .ant-table-column-sort {
      background-color: #c4ebfd;
    }
    .ant-table-tbody > tr.background td {
      background-color: unset!important;
    }
    // .ant-table-tbody > tr.mk-row-active:not(.background) td {
    //   background-color: #91d5ff;
    // }
    // .ant-table-tbody > tr.ant-table-row-selected.mk-row-active:not(.background):hover .ant-table-column-sort {
    //   background-color: #91d5ff;
    // }
    .ant-table-tbody > tr td .anticon.font {
      background-color: unset;
    }
  }
  .ant-table-body {
    overflow-x: auto!important;
    table {
      .ant-table-tbody > tr > td {
        // vertical-align: top;
        .card-cell-list {
          color: rgba(0, 0, 0, 0.85);
        }
        .ant-mk-picture {
          position: relative;
          background-position: center center;
          background-size: cover;
          margin: 2px;
        }
        .ant-mk-picture.scale {
          cursor: zoom-in;
        }
        .action-col {
          .ant-btn > .anticon + span {
            margin-left: 3px;
          }
          button {
            border: 0;
            background-color: transparent;
            color: #1890ff;
            box-shadow: none;
            padding: 0 5px;
            .anticon-loading {
              display: none;
            }
          }
          > div {
            margin: 0 3px;
          }
          > button {
            margin: 0 3px;
          }
          .ant-btn.ant-btn-loading:not(.ant-btn-circle):not(.ant-btn-circle-outline):not(.ant-btn-icon-only) {
            padding-left: 0px;
          }
        }
      }
      .ant-table-tbody > tr > td[rowspan] {
        vertical-align: middle;
      }
      .ant-table-tbody > tr > td.ant-table-column-has-actions {
        .content {
          position: relative;
          z-index: 1;
          word-wrap: break-word;
          word-break: break-word;
        }
      }
      .ant-table-tbody > tr > td {
        position: relative;
        .link-menu {
          position: absolute;
          top: 0px;
          left: 0px;
          right: 0px;
          bottom: 0px;
          opacity: 0;
          cursor: pointer;
        }
      }
      .ant-table-tbody > tr > td .content {
        p {
          margin-bottom: 2px;
        }
        span {
          display: inline-block;
          margin-right: 5px;
        }
      }
      .ant-table-tbody > tr > td .button {
        .ant-btn {
          margin-bottom: 10px;
        }
      }
    }
  }
  // .ant-table-body::-webkit-scrollbar {
  //   width: 8px;
  //   height: 10px;
  // }
  // ::-webkit-scrollbar-thumb {
  //   border-radius: 5px;
  //   box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
  //   background: rgba(0, 0, 0, 0.13);
  // }
  // ::-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);
  // }
  .fix-header {
    .ant-table-body {
      min-height: unset
    }
    .ant-table-placeholder {
      display: none;
    }
    .ant-table-wrapper {
      display: none;
    }
    .ant-affix .ant-table-wrapper {
      display: block;
    }
  }
  .ant-input {
    border: none;
    box-shadow: none!important;
    height: auto;
    border-radius: 0;
  }
  .ant-input-number {
    border: none;
    width: 100%;
    box-shadow: none!important;
    height: auto;
    .ant-input-number-handler-wrap {
      display: none;
    }
    .ant-input-number-input {
      border-radius: 0;
      padding: 0;
      height: auto;
    }
  }
  .editing_table_cell {
    .ant-input {
      padding: 0px;
    }
  }
  td.pointer {
    position: relative;
  }
  td.pointer {
    .mk-mask {
      display: none;
      cursor: pointer;
      position: absolute;
      top: 0;
      left: 0;
      bottom: 0;
      right: 0;
    }
  }
}
.edit-custom-table.editable {
  td {
    background-color: #ffffff!important;
  }
  td.pointer .mk-mask {
    display: block;
  }
  .ant-pagination {
    display: none;
  }
}
.edit-custom-table:not(.fixed-height) {
  .ant-table-body::-webkit-scrollbar {
    width: 8px;
    height: 10px;
  }
  ::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  ::-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);
  }
}
.edit-custom-table.fixed-height {
  .ant-table-body {
    border-bottom: 1px solid rgba(0, 0, 0, .05);
    .ant-table-fixed {
      border-bottom: 0;
    }
  }
}
.edit-custom-table.hidden {
  thead {
    display: none;
  }
}
.edit-custom-table.ghost {
  .main-pickup {
    display: none;
  }
  .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!important;
      }
    }
    tr:hover td {
      background: transparent!important;
    }
  }
}
.image-scale-modal {
  width: 70vw;
  min-height: 80vh;
  top: 10vh;
  .ant-modal-body {
    min-height: calc(80vh - 110px);
    line-height: calc(80vh - 160px);
    text-align: center;
  }
  .ant-modal-footer {
    text-align: center;
    span {
      display: inline-block;
      color: #1890ff;
      padding: 5px 15px;
      cursor: pointer;
    }
  }
}
src/tabviews/custom/components/table/normal-table/index.jsx
@@ -650,9 +650,7 @@
const mapStateToProps = (state) => {
  return {
    menuType: state.editLevel,
    permAction: state.permAction,
    permMenus: state.permMenus
    menuType: state.editLevel
  }
}
src/tabviews/custom/index.jsx
@@ -30,6 +30,7 @@
const TableCard = asyncComponent(() => import('./components/card/table-card'))
const MainSearch = asyncComponent(() => import('@/tabviews/zshare/topSearch'))
const NormalTable = asyncComponent(() => import('./components/table/normal-table'))
const EditTable = asyncComponent(() => import('./components/table/edit-table'))
const NormalGroup = asyncComponent(() => import('./components/group/normal-group'))
const BraftEditor = asyncComponent(() => import('./components/editor/braft-editor'))
const SandBox = asyncComponent(() => import('./components/code/sand-box'))
@@ -522,7 +523,7 @@
        item.search = Utils.initSearchVal(item.search)
      }
      if (item.type === 'table' && item.subtype === 'normaltable') {
      if (item.type === 'table' && (item.subtype === 'normaltable' || item.subtype === 'editable')) {
        let statFields = []
        let getCols = (cols) => {
          return cols.filter(col => {
@@ -697,7 +698,7 @@
            return cell.eleType !== 'button' || skip || permAction[cell.uuid]
          })
        })
      } else if (item.type === 'table' && item.subtype === 'normaltable') {
      } else if (item.type === 'table' && (item.subtype === 'normaltable' || item.subtype === 'editable')) {
        item.cols = item.cols.filter(col => {
          if (col.type !== 'action') return true
          col.elements = col.elements.filter(cell => {
@@ -1104,6 +1105,12 @@
            <NormalTable config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'table' && item.subtype === 'editable') {
        return (
          <Col span={item.width} key={item.uuid}>
            <EditTable config={item} data={data} BID={_bid} mainSearch={mainSearch} menuType={menuType} />
          </Col>
        )
      } else if (item.type === 'group' && item.subtype === 'normalgroup') {
        return (
          <Col span={item.width} key={item.uuid}>
src/templates/sharecomponent/actioncomponent/actionform/index.jsx
@@ -134,7 +134,7 @@
          } else if (['innerpage', 'tab', 'popview', 'excelIn'].includes(_opentype)) {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl'].includes(op.value))
          } else if (card.sqlType === 'insert') {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl', 'required'].includes(op.value))
            item.options = this.state.requireOptions.filter(op => ['notRequired'].includes(op.value))
          } else {
            item.options = this.state.requireOptions
          }
@@ -364,7 +364,7 @@
      this.setState({
        formlist: this.state.formlist.map(item => {
          if (item.key === 'Ot' && value === 'insert') {
            item.options = this.state.requireOptions.filter(op => ['notRequired', 'requiredSgl', 'required'].includes(op.value))
            item.options = this.state.requireOptions.filter(op => ['notRequired'].includes(op.value))
          } else if (item.key === 'Ot') {
            item.options = this.state.requireOptions
          }