king
2020-06-18 f59a500d24291d7f54b71dcca939a2a23dedca7c
src/templates/comtableconfig/index.jsx
@@ -4,35 +4,35 @@
import { is, fromJS } from 'immutable'
import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import { Button, Card, Modal, Collapse, notification, Spin, Icon, Switch, Tooltip } from 'antd'
import { Button, Card, Modal, Collapse, notification, Spin, Icon, Switch, Tooltip, Col } from 'antd'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import { getMainMenuForm } from '@/templates/zshare/formconfig'
import TableComponent from '@/templates/sharecomponent/tablecomponent'
import FieldsComponent from '@/templates/sharecomponent/fieldscomponent'
// import ChartGroupComponent from '@/templates/sharecomponent/chartgroupcomponent'
import asyncComponent from '@/utils/asyncComponent'
import SearchComponent from '@/templates/sharecomponent/searchcomponent'
import ActionComponent from '@/templates/sharecomponent/actioncomponent'
import ColumnComponent from '@/templates/sharecomponent/columncomponent'
import SettingForm from './settingform'
import TabForm from '@/templates/zshare/tabform'
import MenuForm from '@/templates/zshare/menuform'
import TabDragElement from '@/templates/zshare/tabdragelement'
import EditComponent from '@/templates/zshare/editcomponent'
import SourceElement from '@/templates/zshare/dragsource'
import CreateFunc from '@/templates/zshare/createfunc'
import CreateInterface from '@/templates/zshare/createinterface'
import Source from './source'
import './index.scss'
const { Panel } = Collapse
const { confirm } = Modal
const CommonDict = (!localStorage.getItem('lang') || localStorage.getItem('lang') === 'zh-CN') ? zhCN : enUS
const SettingComponent = asyncComponent(() => import('@/templates/sharecomponent/settingcomponent'))
const TableComponent = asyncComponent(() => import('@/templates/sharecomponent/tablecomponent'))
const FieldsComponent = asyncComponent(() => import('@/templates/sharecomponent/fieldscomponent'))
const ChartGroupComponent = asyncComponent(() => import('@/templates/sharecomponent/chartgroupcomponent'))
const ChartComponent = asyncComponent(() => import('@/templates/sharecomponent/chartcomponent'))
const CardComponent = asyncComponent(() => import('@/templates/sharecomponent/cardcomponent'))
const TabsComponent = asyncComponent(() => import('@/templates/sharecomponent/tabscomponent'))
class ComTableConfig extends Component {
  static propTpyes = {
@@ -43,21 +43,17 @@
  }
  state = {
    dict: CommonDict,        // 字典
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    config: null,            // 页面配置
    tableFields: [],         // 表格显示列
    fields: null,            // 搜索条件及显示列,可选字段
    menuformlist: null,      // 基本信息表单字段
    formlist: null,          // 搜索条件、按钮、显示列表单字段
    modaltype: '',           // 模态框类型,控制模态框显示
    card: null,              // 编辑元素
    menuloading: false,      // 菜单保存中
    menucloseloading: false, // 菜单关闭时,选择保存
    loading: false,          // 加载中,页面spin
    settingVisible: false,   // 全局配置模态框
    closeVisible: false,     // 关闭模态框
    tables: [],              // 可用表名
    selectedTables: [],      // 已选表名
    originMenu: null,        // 原始菜单
    originActions: null,     // 原始按钮信息,使用已有用户模板
    delActions: [],          // 删除按钮列表
@@ -66,8 +62,9 @@
    optionLibs: null,        // 自定义下拉选项库
    thawButtons: [],         // 已选择要解冻的按钮
    activeKey: '0',          // 默认展开基本信息
    sqlVerifing: false,      // sql验证
    pasteContent: null       // 粘贴配置信息
    chartview: null,         // 当前视图
    pasteContent: null,      // 粘贴配置信息
    openEdition: ''          // 编辑版本标记,防止多人操作
  }
  /**
@@ -82,9 +79,9 @@
    let _config = ''
    if (!_LongParam) {
      _config = JSON.parse(JSON.stringify(Source.baseConfig))
      _config = fromJS(Source.baseConfig).toJS()
      if (!menu.isSubtable) { // 不是选择主子表时,隐藏标签页
        _config.tabs = []
        _config.tabgroups = [{ uuid: 'tabs', sublist: [] }]
      }
      _config.isAdd = true
    } else {
@@ -106,12 +103,76 @@
      })
    }
    // 配置默认值,兼容
    _config.tabs = _config.tabs || []
    _config.tabgroups = _config.tabgroups || ['tabs']
    _config.setting.subtabs = _config.setting.subtabs || []
    _config.Template = 'CommonTable'
    _config.easyCode = _config.easyCode || ''
    if (!_config.version || _config.version < '1.0') {
      // 配置默认值,兼容
      _config.version = '1.0'
      _config.Template = 'CommonTable'
      _config.easyCode = _config.easyCode || ''
      if (!_config.tabgroups) {
        _config.tabgroups = [{ uuid: 'tabs', sublist: [] }]
      } else if (typeof(_config.tabgroups[0]) === 'string') {
        let _tabgroups = []
        _config.tabgroups.forEach(groupId => {
          let _group = {
            uuid: groupId,
            sublist: fromJS(_config[groupId]).toJS()
          }
          delete _config[groupId]
          _tabgroups.push(_group)
        })
        _config.tabgroups = _tabgroups
      }
      // 兼容图表
      if (!_config.charts) {
        _config.expand = false
        _config.charts = [{
          uuid: Utils.getuuid(),
          label: '',
          title: '',
          chartType: 'table',
          icon: 'table',
          Hide: 'false',
          blacklist: []
        }]
      } else {
        _config.charts.forEach(card => {
          if (card.chartType === 'card') {
            card.details = card.details.map(_cell => {
              if (!_cell.fontSize) {
                _cell.fontSize = 14
              }
              if (!_cell.width) {
                _cell.width = 100
              } else if (_cell.width === 'helf') {
                _cell.width = 50
              } else if (_cell.width === 'third') {
                _cell.width = 33
              }
              if (_cell.bold === 'true') {
                _cell.fontWeight = 'normal'
              }
              if (!_cell.height) {
                _cell.height = 1
              }
              return _cell
            })
            if (card.widthType === 'ratio' && card.avatar && card.avatar.widthType !== 'ratio') {
              card.avatar.widthType = 'ratio'
              card.avatar.width = 32
            }
          }
        })
      }
    }
    
    let _oriActions = []
    if (_config.type === 'user') {
@@ -124,13 +185,13 @@
        if (item.OpenType === 'pop') { // 含有子配置项的按钮(表单)
          _oriActions.push({
            prebtn: JSON.parse(JSON.stringify(item)),
            prebtn: fromJS(item).toJS(),
            curuuid: uuid,
            Template: 'Modal'
          })
        } else if (item.OpenType === 'tab' || item.OpenType === 'blank') { // 含有子配置项的按钮(标签后当前页打开)
          _oriActions.push({
            prebtn: JSON.parse(JSON.stringify(item)),
            prebtn: fromJS(item).toJS(),
            curuuid: uuid,
            Template: item.tabTemplate
          })
@@ -142,7 +203,7 @@
      // 重置标签ID
      _config.tabgroups.forEach(group => {
        _config[group] = _config[group].map(tab => {
        group.sublist = group.sublist.map(tab => {
          tab.uuid = Utils.getuuid()
          
          if (tab.linkTab) {
@@ -155,104 +216,22 @@
    }
    this.setState({
      chartview: _config.charts[0].uuid,
      config: _config,
      openEdition: menu.open_edition || '',
      activeKey: menu.activeKey || '0',
      optionLibs: optionLibs,
      originActions: _oriActions,
      originMenu: JSON.parse(JSON.stringify(menu)),
      selectedTables: _config.tables || [],
      menuformlist: [
        {
          type: 'select',
          key: 'fstMenuId',
          label: '一级菜单',
          initVal: menu.fstMenuId,
          required: true,
          readonly: false,
          options: menu.fstMenuList
        },
        {
          type: 'select',
          key: 'parentId',
          label: '二级菜单',
          initVal: menu.ParentID,
          required: true,
          readonly: false,
          options: menu.supMenuList
        },
        {
          type: 'text',
          key: 'menuName',
          label: this.state.dict['header.menu.menuName'],
          initVal: menu.MenuName,
          required: true,
          readonly: false
        },
        {
          type: 'text',
          key: 'menuNo',
          label: this.state.dict['header.menu.menuNo'],
          initVal: menu.MenuNo,
          required: true,
          readonly: false
        },
        {
          type: 'select',
          key: 'opentype',
          label: this.state.dict['header.menu.openType'],
          initVal: menu.PageParam.OpenType,
          required: true,
          options: [{
            MenuID: 'newtab',
            text: this.state.dict['header.form.tab']
          }, {
            MenuID: 'newpage',
            text: this.state.dict['header.form.newpage']
          }, {
            MenuID: 'currenttab',
            text: this.state.dict['header.form.currenttab']
          }]
        },
        {
          type: 'text',
          key: 'easyCode',
          label: this.state.dict['header.form.easyCode'],
          initVal: _config.easyCode,
          required: false,
          readonly: false
        }
      ]
      originMenu: fromJS(menu).toJS(),
      menuformlist: getMainMenuForm(menu, _config)
    })
  }
  /**
   * @description 加载完成后
   * 1、获取系统可使用表
   * 2、根据配置信息中已使用表获取相关字段信息
   * 3、获取所有标签页信息
   * @description 加载完成后, 获取所有标签页信息
   */
  componentDidMount () {
    Api.getSystemConfig({func: 'sPC_Get_UserTemp', TypeCharTwo: 'tab'}).then(res => {
      if (res.status) {
        this.setState({
          tabviews: res.UserTemp.map(temp => {
            return {
              uuid: temp.MenuID,
              value: temp.MenuID,
              text: temp.MenuName,
              type: temp.Template,
              MenuNo: temp.MenuNo
            }
          })
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
    this.reloadTab(false)
  }
  /**
@@ -267,9 +246,9 @@
  /**
   * @description 加载或刷新标签信息
   */
  reloadTab = () => {
  reloadTab = (type) => {
    this.setState({
      loading: true,
      loading: type,
      tabviews: []
    })
    Api.getSystemConfig({func: 'sPC_Get_UserTemp', TypeCharTwo: 'tab'}).then(res => {
@@ -286,11 +265,14 @@
            }
          })
        })
        notification.success({
          top: 92,
          message: '刷新成功。',
          duration: 2
        })
        if (type) {
          notification.success({
            top: 92,
            message: '刷新成功。',
            duration: 2
          })
        }
      } else {
        this.setState({
          loading: false
@@ -302,316 +284,6 @@
        })
      }
    })
  }
  /**
   * @description 元素添加或拖动时顺序变化
   */
  handleList = (type, list, card) => {
    const { config } = this.state
    if (list.length > config[card.groupId].length) {
      list = list.filter(item => !item.origin)
      this.handleTab(card)
    }
    this.setState({config: {...config, [card.groupId]: list}})
  }
  /**
   * @description 标签编辑,筛选可选的下级标签与已关联的下级标签
   */
  handleTab = (card) => {
    const { config } = this.state
    let menus = [
      {value: '', text: '空'},
      {value: 'mainTable', text: '主表'}
    ]
    let equalTabs = []
    let supMenu = card.supMenu || ''
    let equalTab = card.equalTab || []
    let isuptab = true
    let equalTabIds = []
    config.tabgroups.forEach((groupId, i) => {
      if (groupId === card.groupId) {
        isuptab = false
        config[card.groupId].forEach(tab => { // 可关联的同级标签
          if (tab.uuid === card.uuid) return
          equalTabIds.push(tab.uuid)
          equalTabs.push(tab)
        })
      } else if (isuptab) {
        config[groupId].forEach(tab => {
          menus.push({
            value: tab.uuid,
            text: tab.label
          })
        })
      }
    })
    if (supMenu && menus.filter(menu => menu.value === supMenu).length === 0) {
      supMenu = ''
    }
    if (equalTab.length > 0) {
      equalTab = equalTab.filter(tabId => equalTabIds.includes(tabId))
    }
    this.setState({
      modaltype: 'tabs',
      card: card,
      formlist: [
        {
          type: 'text',
          key: 'label',
          label: this.state.dict['header.menu.tabName'],
          initVal: card.label || '',
          required: true
        },
        {
          type: 'select',
          key: 'type',
          label: this.state.dict['header.form.tabType'],
          initVal: card.type || 'SubTable',
          required: true,
          options: [{
            value: 'SubTable',
            text: this.state.dict['header.menu.tab.subtable']
          }]
        },
        {
          type: 'select',
          key: 'linkTab',
          label: this.state.dict['header.form.linkTab'],
          initVal: card.linkTab || '',
          required: false,
          options: []
        },
        {
          type: 'select',
          key: 'icon',
          label: this.state.dict['header.menu.icon'],
          initVal: card.icon || '',
          required: false,
          options: [{
            value: '',
            text: this.state.dict['header.form.empty']
          }, {
            value: 'table',
            text: 'table'
          }, {
            value: 'bar-chart',
            text: 'bar-chart'
          }, {
            value: 'pie-chart',
            text: 'pie-chart'
          }, {
            value: 'line-chart',
            text: 'line-chart'
          }]
        },
        {
          type: 'select',
          key: 'supMenu',
          label: this.state.dict['header.form.supTab'],
          initVal: supMenu,
          required: false,
          options: menus
        },
        {
          type: 'mutilselect',
          key: 'equalTab',
          label: this.state.dict['header.form.equalTab'],
          tooltip: '如果子标签中含有刷新同级标签的按钮,在此处添加需要刷新的标签。',
          initVal: equalTab,
          required: false,
          options: equalTabs
        },
        {
          type: 'text',
          key: 'foreignKey',
          label: '外键',
          tooltip: '外键旨在标签页中执行默认函数(添加)时,替换BID字段',
          initVal: card.foreignKey || '',
          required: false
        },
        {
          type: 'radio',
          key: 'searchPass',
          label: '主表搜索',
          initVal: card.searchPass || 'false',
          tooltip: '使用主表搜索条件时,主表的搜索条件会传入子表中。',
          required: false,
          options: [{
            value: 'true',
            text: '使用'
          }, {
            value: 'false',
            text: '不使用'
          }]
        }
      ]
    })
  }
  /**
   * @description 标签修改后提交保存
   */
  handleSubmit = () => {
    const { config } = this.state
    this.tabsFormRef.handleConfirm().then(res => {
      let _tabgroup = config[res.groupId].map(item => {
        if (item.uuid === res.uuid) {
          return res
        } else {
          return item
        }
      })
      _tabgroup = _tabgroup.filter(item => !item.origin)
      this.setState({
        config: {...config, [res.groupId]: _tabgroup},
        modaltype: ''
      })
    })
  }
  /**
   * @description 取消保存,如果元素为新添元素,则从序列中删除
   */
  editModalCancel = () => {
    const { config, card } = this.state
    if (card && card.focus) {
      let _config = null
      let _tabgroup = config[card.groupId].filter(item => item.uuid !== card.uuid)
      _config = {...config, [card.groupId]: _tabgroup}
      this.setState({
        card: null,
        config: _config,
        modaltype: ''
      })
    } else {
      this.setState({
        card: null,
        modaltype: ''
      })
    }
  }
  /**
   * @description 创建表格存储过程
   */
  tableCreatFunc = () => {
    const { menu } = this.props
    const { config } = this.state
    this.settingRef.handleConfirm().then(setting => {
      if (!(setting.interType === 'inner') || !setting.innerFunc) {
        notification.warning({
          top: 92,
          message: '接口类型为-内部,且存在内部函数时,才可以创建存储过程!',
          duration: 5
        })
        return
      }
      let _config = {...config, setting: setting}
      let newLText = Utils.formatOptions(Utils.getTableFunc(setting, menu, _config)) // 创建存储过程sql
      let DelText = Utils.formatOptions(Utils.dropfunc(setting.innerFunc))          // 删除存储过程sql
      this.refs.tableCreatFunc.exec(setting.innerFunc, newLText, DelText).then(result => {
        if (result === 'success') {
          this.setState({
            config: _config
          })
        }
      })
    })
  }
  /**
   * @description 创建表格接口(读出)
   */
  tableCreatInterface = () => {
    const { menu } = this.props
    const { config } = this.state
    this.menuformRef.handleConfirm().then(res => {
      this.settingRef.handleConfirm().then(setting => {
        if (setting.interType !== 'inner' || setting.innerFunc) {
          notification.warning({
            top: 92,
            message: '接口类型为-内部,且不存在内部函数时,才可以创建接口!',
            duration: 5
          })
          return
        }
        let _config = {...config, setting: setting}
        let _menu = {
          type: 'main',
          MenuID: menu.MenuID,
          menuName: res.menuName,
          menuNo: res.menuNo
        }
        this.refs.tableCreatInterface.triggerOutInterface(_menu, _config)
      })
    })
  }
  deleteElement = (element) => {
    const { config, thawButtons } = this.state
    let _this = this
    confirm({
      content: `确定删除<<${element.card.label}>>吗?`,
      okText: this.state.dict['model.confirm'],
      cancelText: this.state.dict['header.cancel'],
      onOk() {
        let _config = null
        if (element.type === 'tabs') {
          let _tabgroup = config[element.card.groupId].filter(item => {
            if (item.uuid === element.card.uuid) {
              return false
            } else {
              return true
            }
          })
          _config = {...config, [element.card.groupId]: _tabgroup}
        }
        // 删除按钮元素
        let _delActions = _this.state.delActions
        if (element.type === 'action' || element.type === 'tabs') {
          _delActions.push(element)
        }
        _this.setState({
          config: _config,
          delActions: _delActions,
          thawButtons: thawButtons.filter(key => key !== element.card.uuid)
        })
      },
      onCancel() {}
    })
  }
  /**
   * @description 三级菜单切换模板
   */
  changeTemplate = () => {
    this.props.handleView({tabview: 'template'})
  }
  getFuncNames = (data, funcNames, tableNames) => {
@@ -643,54 +315,16 @@
   */
  submitConfig = () => {
    const { menu } = this.props
    const { originMenu, delActions, thawButtons } = this.state
    const { originMenu, delActions, thawButtons, openEdition } = this.state
    let config = JSON.parse(JSON.stringify(this.state.config))
    let config = fromJS(this.state.config).toJS()
    this.menuformRef.handleConfirm().then(res => {
      if (config.isAdd) {
        if (config.search[0] && config.search[0].origin) {
          config.search = config.search.filter(item => !item.origin)
        }
        if (config.action[0] && config.action[0].origin) {
          config.action = config.action.filter(item => !item.origin)
        }
        if (config.columns[0] && config.columns[0].origin) {
          config.columns = config.columns.filter(item => !item.origin)
        }
        if (config.tabs[0] && config.tabs[0].origin) {
          config.tabs = config.tabs.filter(item => !item.origin)
        }
      }
      let btnNames = config.action.map(item => item.label)
      btnNames = Array.from(new Set(btnNames))
      if (btnNames.length < config.action.length) {
        notification.warning({
          top: 92,
          message: '按钮名称不可相同!',
          duration: 5
        })
        return
      }
      let tabNames = []
      let tablength = 0
      config.tabgroups.forEach(group => {
        config[group].forEach(tab => {
          tabNames.push(tab.label)
        })
        tablength += config[group].length
      })
      tabNames = Array.from(new Set(tabNames))
      if (tabNames.length < tablength) {
        notification.warning({
          top: 92,
          message: '标签名称不可相同!',
          duration: 5
        })
        return
        config.search = config.search.filter(item => !item.origin)
        config.action = config.action.filter(item => !item.origin)
        config.columns = config.columns.filter(item => !item.origin)
        config.tabgroups[0].sublist = config.tabgroups[0].sublist.filter(item => !item.origin)
      }
      if (config.type === 'user') { // 使用已有菜单时,默认添加关联标签id
@@ -702,11 +336,10 @@
        })
    
        config.tabgroups.forEach(group => {
          config[group] = config[group].map(tab => {
          group.sublist = group.sublist.map(tab => {
            if (!tab.linkTab) {
              tab.linkTab = Utils.getuuid()
            }
            return tab
          })
        })
@@ -717,16 +350,9 @@
      let _pageParam = {...menu.PageParam, OpenType: res.opentype}
      // 未设置数据源或标签不合法时,启用状态为false
      if (_config.setting.interType === 'inner' && !_config.setting.innerFunc && !_config.setting.dataresource) {
      let vresult = this.verifyconfig(_config)
      if (vresult !== true) {
        _config.enabled = false
      } else if (!_config.setting.primaryKey) {
        _config.enabled = false
      } else if (_config.tabgroups.length > 1) {
        _config.tabgroups.forEach(group => {
          if (_config[group].length === 0) {
            _config.enabled = false
          }
        })
      }
      _config.funcs = [] // 页面及子页面存储过程集
@@ -781,10 +407,8 @@
        }
      })
      _config.tabgroups.forEach(groupId => {
        if (_config[groupId].length === 0) return
        _config[groupId].forEach(tab => {
      _config.tabgroups.forEach(group => {
        group.sublist.forEach(tab => {
          _config.funcs.push({
            type: 'tab',
            subtype: 'tab',
@@ -873,7 +497,7 @@
          func: 'sPC_Button_AddUpt',
          Type: 40,                  // 添加菜单下的按钮type为40,按钮下的按钮type为60
          ParentID: menu.MenuID,
          MenuNo: res.menuNo,
          MenuNo: res.MenuNo,
          Template: menu.PageParam.Template || '',
          PageParam: '',
          LongParam: '',
@@ -909,8 +533,8 @@
        btntabs.forEach(item => {
          _LText.push(`select '${item.uuid}' as MenuID ,'${item.linkTab}' as Tabid,'${item.label}' as TabName ,'${item.sort * 10}' as Sort`)
        })
        _config.tabgroups.forEach(groupId => {
          _config[groupId].forEach(item => {
        _config.tabgroups.forEach(group => {
          group.sublist.forEach(item => {
            _sort++
            _LText.push(`select '${menu.MenuID}' as MenuID ,'${item.linkTab}' as Tabid,'${item.label}' as TabName ,'${_sort * 10}' as Sort`)
          })
@@ -931,10 +555,10 @@
          SndID: res.parentId,
          ParentID: res.parentId,
          MenuID: menu.MenuID,
          MenuNo: res.menuNo,
          MenuNo: res.MenuNo,
          EasyCode: res.easyCode,
          Template: menu.PageParam.Template || '',
          MenuName: res.menuName,
          MenuName: res.MenuName,
          PageParam: JSON.stringify(_pageParam),
          LongParam: _LongParam,
          LText: _vals.func.map(item => `select '${menu.MenuID}' as MenuID,'${item.func}' as ProcName,'${item.label}' as MenuName`),
@@ -952,6 +576,10 @@
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        param.secretkey = Utils.encrypt(param.LText, param.timestamp)
        if (openEdition) { // 版本管理
          param.open_edition = openEdition
        }
        // 有按钮或标签删除时,先进行删除操作
        // 删除成功后,保存页面配置
        new Promise(resolve => {
@@ -959,7 +587,7 @@
            let deffers = delActions.map(item => {
              let _param = {
                func: 'sPC_MainMenu_Del',
                MenuID: item.card.uuid
                MenuID: item.card ? item.card.uuid : item.uuid
              }
              if (item.type === 'action') {
@@ -1054,7 +682,7 @@
          }
        }).then(resp => {
          if (resp === false) return
          let localParam = JSON.parse(JSON.stringify(param))
          let localParam = fromJS(param).toJS()
          Api.getSystemConfig(param).then(response => {
            if (response.status) {
@@ -1066,18 +694,19 @@
              this.setState({
                config: _config,
                openEdition: response.open_edition || '',
                originMenu: {
                  ...originMenu,
                  LongParam: _config,
                  PageParam: _pageParam,
                  MenuName: res.menuName,
                  MenuNo: res.menuNo,
                  MenuName: res.MenuName,
                  MenuNo: res.MenuNo,
                  ParentID: res.parentId,
                  fstMenuId: res.fstMenuId,
                  supMenuList: _supMenuList
                }
              })
              this.props.reloadmenu()
              
              this.submitAction(btnParam, tabParam)
@@ -1087,6 +716,8 @@
              delete localParam.PageParam
              delete localParam.Template
              delete localParam.Sort
              delete localParam.EasyCode
              delete localParam.open_edition
              Api.getLocalConfig(localParam)
            } else {
@@ -1214,13 +845,27 @@
                    return _btn
                  })
                  _subconfig.tabgroups.forEach(_groupId => {
                    _subconfig[_groupId] = _subconfig[_groupId].map(_tab => {
                      _tab.uuid = Utils.getuuid()
                      if (_tab.linkTab) {
                        _tab.linkTab = ''
                  // 兼容已有结构
                  if (!_subconfig.tabgroups) {
                    _subconfig.tabgroups = [{ uuid: 'tabs', sublist: [] }]
                  } else if (typeof(_subconfig.tabgroups[0]) === 'string') {
                    let _tabgroups = []
                    _subconfig.tabgroups.forEach(groupId => {
                      let _group = {
                        uuid: groupId,
                        sublist: fromJS(_subconfig[groupId]).toJS()
                      }
                      delete _subconfig[groupId]
                      _tabgroups.push(_group)
                    })
                    _subconfig.tabgroups = _tabgroups
                  }
                  _subconfig.tabgroups.forEach(group => {
                    group.sublist = group.sublist.map(_tab => {
                      _tab.uuid = Utils.getuuid()
                      _tab.linkTab = Utils.getuuid()
                      return _tab
                    })
@@ -1306,8 +951,8 @@
          ...originMenu,
          LongParam: _config,
          PageParam: _pageParam,
          MenuName: res.menuName,
          MenuNo: res.menuNo,
          MenuName: res.MenuName,
          MenuNo: res.MenuNo,
          ParentID: res.parentId,
          fstMenuId: res.fstMenuId
        }
@@ -1328,115 +973,11 @@
  }
  /**
   * @description 页面配置信息模态框显示
   */
  changeSetting = () => {
    this.setState({
      settingVisible: true
    })
  }
  /**
   * @description 保存页面配置信息
   */
  settingSave = () => {
    const { menu } = this.props
    const {config} = this.state
    this.settingRef.handleConfirm().then(res => {
      if (
        res.interType === 'inner' &&
        !res.innerFunc &&
        /[^\s]+\s+[^\s]+/ig.test(res.dataresource) &&
        config.setting.dataresource !== res.dataresource
      ) {
        let param = {
          func: 's_DataSrc_Save',
          LText: res.dataresource,
          MenuID: menu.MenuID
        }
        param.LText = Utils.formatOptions(param.LText)
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        param.secretkey = Utils.encrypt(param.LText, param.timestamp)
        Api.getLocalConfig(param)
      }
      if (res.interType === 'inner' && !res.innerFunc && /\s/.test(res.dataresource)) {
        this.setState({
          sqlVerifing: true
        })
        let _dataresource = res.dataresource
        if (res.queryType === 'statistics') {
          let fieldmap = new Map()
          let options = config.search.map(item => {
            let _field = item.key
            let _val = ''
            if (fieldmap.has(_field)) {
              _field = _field + '1'
            }
            fieldmap.set(item.key, true)
            if (/date/.test(item.type)) {
              _val = '1900-01-01'
            }
            return {
              reg: new RegExp('@' + _field + '@', 'ig'),
              value: _val
            }
          })
          options.forEach(item => {
            _dataresource = _dataresource.replace(item.reg, `'${item.value}'`)
          })
        }
        let param = {
          func: 's_debug_sql',
          LText: _dataresource
        }
        param.LText = Utils.formatOptions(param.LText)
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        param.secretkey = Utils.encrypt(param.LText, param.timestamp)
        Api.getLocalConfig(param).then(result => {
          if (result.status) {
            this.setState({
              sqlVerifing: false,
              config: {...config, setting: res},
              settingVisible: false
            })
          } else {
            this.setState({sqlVerifing: false})
            Modal.error({
              title: result.message
            })
          }
        })
      } else {
        this.setState({
          config: {...config, setting: res},
          settingVisible: false
        })
      }
    })
  }
  /**
   * @description 设置可配置按钮
   */
  setSubConfig = (item, type) => {
    const { menu } = this.props
    const { config, originMenu, optionLibs, activeKey } = this.state
    const { config, originMenu, optionLibs, activeKey, openEdition } = this.state
    if (!originMenu.MenuID) { // menuID不存在时,为新建菜单,提示菜单尚未保存
      notification.warning({
@@ -1446,14 +987,14 @@
      })
    } else {
      this.menuformRef.handleConfirm().then(res => {
        let _config = {...config, tables: this.state.selectedTables, easyCode: res.easyCode}
        let _config = {...config, easyCode: res.easyCode}
        let _pageParam = {...menu.PageParam, OpenType: res.opentype}
        let _originMenu = {
          ...originMenu,
          LongParam: _config,
          PageParam: _pageParam,
          MenuName: res.menuName,
          MenuNo: res.menuNo,
          MenuName: res.MenuName,
          MenuNo: res.MenuNo,
          ParentID: res.parentId,
          fstMenuId: res.fstMenuId
        }
@@ -1488,8 +1029,8 @@
          isbutton = false
        }
        // 保存当前打开页签
        _originMenu.activeKey = activeKey
        _originMenu.activeKey = activeKey       // 保存当前打开页签
        _originMenu.open_edition = openEdition  // 更新版本号
        let param = {
          optionLibs: optionLibs,
@@ -1503,6 +1044,11 @@
          editAction: isbutton ? item : '',
          subConfig: '',
          tabview: _view
        }
        // 当子表使用主页搜索条件时,将主页搜索向下传递
        if (param.editTab && param.editTab.searchPass === 'true') {
          param.editTab.mainsearch = fromJS(_config.search).toJS()
        }
        this.setState({
@@ -1535,6 +1081,14 @@
              param.subConfig = _LongParam
            }
            if (param.editTab) {
              param.editTab.open_edition = res.open_edition || ''
            } else if (param.editAction) {
              param.editAction.open_edition = res.open_edition || ''
            } else if (param.btnTab) {
              param.btnTab.open_edition = res.open_edition || ''
            }
            this.props.handleView(param)
          } else {
            this.setState({
@@ -1563,107 +1117,62 @@
  onEnabledChange = () => {
    const { config } = this.state
    let _enabled = !config.enabled
    let result = this.verifyconfig(config)
    if (_enabled && result !== true) {
      notification.warning({
        top: 92,
        message: result,
        duration: 5
      })
      return
    }
    this.setState({
      config: {...config, enabled: _enabled}
    })
  }
  /**
   * @description 校验配置信息的合法性
   */
  verifyconfig = (config) => {
    let tabinvalid = true
    if (config.tabgroups.length > 1) {
      config.tabgroups.forEach(group => {
        if (config[group].length === 0) {
        if (group.sublist.length === 0) {
          tabinvalid = false
        }
      })
    }
    if (config.setting.interType === 'inner' && !config.setting.innerFunc && !config.setting.dataresource) {
      notification.warning({
        top: 92,
        message: '菜单尚未设置数据源,不可启用!',
        duration: 5
      })
    let charterr = ''
    config.charts.forEach(chart => {
      if (!charterr && ['line', 'bar', 'pie'].includes(chart.chartType) && !chart.Xaxis) {
        charterr = '图表' + (chart.title ? '《' + chart.title + '》' : '') + '坐标轴未设置,不可启用!'
      }
    })
    let hasKey = false
    config.columns.forEach(col => {
      if (config.setting.primaryKey === col.field) {
        hasKey = true
      }
    })
    if (config.setting.interType === 'inner' && !config.setting.innerFunc && config.setting.default !== 'false' && !config.setting.dataresource) {
      return '菜单尚未设置数据源,不可启用!'
    } else if (!config.setting.primaryKey) {
      notification.warning({
        top: 92,
        message: '菜单尚未设置主键,不可启用!',
        duration: 5
      })
      return '菜单尚未设置主键,不可启用!'
    } else if (!hasKey) {
      return '显示列中不存在主键字段,不可启用!'
    } else if (!tabinvalid) {
      notification.warning({
        top: 92,
        message: '菜单标签页设置错误(多行标签内,行标签不可为空),不可启用!',
        duration: 5
      })
      return '菜单标签页设置错误(存在多行标签时,行标签不可为空)!'
    } else if (charterr) {
      return charterr
    } else {
      this.setState({
        config: {...config, enabled: !config.enabled}
      })
      return true
    }
  }
  /**
   * @description 增加标签页分组
   */
  addTabGroup = () => {
    let _this = this
    let _config = JSON.parse(JSON.stringify(this.state.config))
    confirm({
      content: `确定新建标签组吗?`,
      okText: this.state.dict['model.confirm'],
      cancelText: this.state.dict['header.cancel'],
      onOk() {
        let newgroup = 'tabs' + Utils.getuuid()
        _config.tabgroups.push(newgroup)
        _config[newgroup] = []
        _this.setState({
          config: _config
        })
      },
      onCancel() {}
    })
  }
  /**
   * @description 删除标签页分组
   */
  delTabGroup = (groupId) => {
    let _this = this
    let _config = JSON.parse(JSON.stringify(this.state.config))
    confirm({
      content: `确定删除标签组吗?`,
      okText: this.state.dict['model.confirm'],
      cancelText: this.state.dict['header.cancel'],
      onOk() {
        _config.tabgroups = _config.tabgroups.filter(group => group !== groupId)
        delete _config[groupId]
        _this.setState({
          config: _config
        })
      },
      onCancel() {}
    })
  }
  handleGroup = (index, type) => {
    let config = JSON.parse(JSON.stringify(this.state.config))
    if (type === 'up') {
      config.tabgroups.splice(index, 0, config.tabgroups.splice(index - 1, 1)[0])
    } else {
      config.tabgroups.splice(index, 0, config.tabgroups.splice(index + 1, 1)[0])
    }
    this.setState({
      config: config
    })
    notification.success({
      top: 92,
      message: '调整成功',
      duration: 2
    })
  }
  /**
@@ -1683,7 +1192,7 @@
  /**
   * @description 编辑功能完成更新,包括解冻按钮、粘贴、替换等
   */
  updateConfig = (res) => {
  editConfig = (res) => {
    if (res.type === 'thaw') {
      this.setState({
        thawButtons: res.thawButtons,
@@ -1726,15 +1235,6 @@
  }
  /**
   * @description 更新显示列配置信息
   */
  updatecolumn = (config) => {
    this.setState({
      config: config
    })
  }
  /**
   * @description 更新图表组配置信息
   */
  updatechartgroup = (config, _chartview) => {
@@ -1757,27 +1257,38 @@
  }
  /**
   * @description 批量添加,更新配置信息
   * @description 更新标签配置信息
   */
  updatefield = (config) => {
  updatetabs = (config, delcards) => {
    const { delActions } = this.state
    this.setState({
      config: config,
      delActions: delcards ? [...delActions, ...delcards] : delActions
    })
  }
  /**
   * @description 更新配置信息
   */
  updateconfig = (config) => {
    this.setState({
      config: config
    })
  }
  render () {
    const { modaltype, activeKey, config } = this.state
    const { activeKey, config, chartview } = this.state
    const confActions = config.action.filter(_action => !_action.origin && ['pop', 'popview', 'blank', 'tab'].includes(_action.OpenType))
    let configTabs = []
    config.tabgroups.forEach(group => {
      configTabs.push(...config[group])
      configTabs.push(...group.sublist)
    })
    return (
      <div className="common-table-board">
        {/* <div className="ant-modal-mask"></div> */}
        <DndProvider backend={HTML5Backend}>
          {/* 工具栏 */}
          <div className="tools">
@@ -1794,30 +1305,25 @@
                <TableComponent
                  config={config}
                  containerId="main-basedata"
                  selectedTables={config.tables || []}
                  updatetable={this.updatetable}
                />
              </Panel>
              {/* 搜索条件添加 */}
              <Panel header={this.state.dict['header.menu.search']} key="1">
                <div className="search-element">
                  {Source.searchItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                  {Source.searchItems.map((item, index) => (<SourceElement key={index} content={item}/>))}
                </div>
                <FieldsComponent
                  config={config}
                  type="search"
                  tableFields={this.state.tableFields}
                  updatefield={this.updatefield}
                  updatefield={this.updateconfig}
                />
              </Panel>
              {/* 按钮添加 */}
              <Panel header={this.state.dict['header.menu.action']} key="2">
                <div className="search-element">
                  {Source.actionItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                  {Source.actionItems.map((item, index) => (<SourceElement key={index} content={item}/>))}
                </div>
                <div className="config-btn">
                  {confActions.length > 0 ?
@@ -1845,23 +1351,19 @@
              {/* 添加显示列 */}
              <Panel header={this.state.dict['header.menu.column']} key="3">
                <div className="search-element">
                  {Source.columnItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                  {Source.columnItems.map((item, index) => (<SourceElement key={index} content={item}/>))}
                </div>
                <FieldsComponent
                  config={config}
                  type="columns"
                  tableFields={this.state.tableFields}
                  updatefield={this.updatefield}
                  updatefield={this.updateconfig}
                />
              </Panel>
              {/* 添加标签 */}
              <Panel header={this.state.dict['header.menu.tab']} key="4">
                <div className="search-element">
                  {Source.tabItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                  {Source.tabItems.map((item, index) => (<SourceElement key={index} content={item}/>))}
                </div>
                {configTabs.length > 0 ?
                  <p className="config-btn-title">
@@ -1890,18 +1392,24 @@
            <Card title={
              <div>
                {this.state.dict['header.menu.page.configurable']} 
                <Icon type="redo" style={{marginLeft: '10px'}} title="刷新标签列表" onClick={this.reloadTab} />
                <Icon type="redo" style={{marginLeft: '10px'}} title="刷新标签列表" onClick={() => this.reloadTab(true)} />
              </div>
            } bordered={false} extra={
              <div>
                <EditComponent dict={this.state.dict} type="maintable" config={this.state.config} MenuID={this.props.menu.MenuID} thawButtons={this.state.thawButtons} refresh={this.updateConfig}/>
                <EditComponent dict={this.state.dict} type="maintable" config={this.state.config} MenuID={this.props.menu.MenuID} thawButtons={this.state.thawButtons} refresh={this.editConfig}/>
                <Switch className="big" checkedChildren="启" unCheckedChildren="停" checked={this.state.config.enabled} onChange={this.onEnabledChange} />
                <Button type="primary" onClick={this.changeTemplate}>{this.state.dict['header.menu.template.change']}</Button>
                <Button type="primary" onClick={this.submitConfig} loading={this.state.menuloading}>{this.state.dict['header.save']}</Button>
                <Button onClick={this.cancelConfig}>{this.state.dict['header.return']}</Button>
              </div>
            } style={{ width: '100%' }}>
              <Icon type="setting" onClick={this.changeSetting} />
              <SettingComponent
                type="main"
                config={config}
                MenuID={this.props.menu.MenuID}
                menuformRef={this.menuformRef}
                permFuncField={this.props.permFuncField}
                updatesetting={this.updateconfig}
              />
              <SearchComponent
                menu={{MenuID: this.props.menu.MenuID, MenuName: this.props.menu.MenuName}}
                config={config}
@@ -1910,101 +1418,74 @@
                optionLibs={this.state.optionLibs}
                updatesearch={this.updatesearch}
              />
              <ActionComponent
                type="main"
                menu={{MenuID: this.props.menu.MenuID, MenuName: this.props.menu.MenuName, MenuNo: this.props.menu.MenuNo}}
              <div className="chart-view" style={{position: 'relative'}}>
                {/* 视图组 权限 会员等级20+ */}
                {this.props.memberLevel >= 20 ? <ChartGroupComponent
                  config={config}
                  sysRoles={this.props.sysRoles}
                  updatechartgroup={this.updatechartgroup}
                /> : null}
                {config.charts.map(item => {
                  if (!config.expand && chartview !== item.uuid) return ''
                  if (item.chartType === 'table') {
                    return (
                      <Col span={item.width || 24} key={item.uuid}>
                        {config.charts.length > 1 && item.title ? <p className="chart-title">{item.title}</p> : null}
                        <ActionComponent
                          type="main"
                          menu={{ MenuID: this.props.menu.MenuID, MenuName: this.props.menu.MenuName, MenuNo: this.props.menu.MenuNo }}
                          config={config}
                          tabs={this.state.tabviews}
                          menuformRef={this.menuformRef}
                          pasteContent={this.state.pasteContent}
                          usefulFields={this.props.permFuncField}
                          setSubConfig={(_btn) => this.setSubConfig(_btn, 'button')}
                          updateaction={this.updateaction}
                        />
                        <ColumnComponent
                          config={config}
                          menu={this.props.menu}
                          sysRoles={this.props.sysRoles}
                          pasteContent={this.state.pasteContent}
                          updatecolumn={this.updateconfig}
                        />
                      </Col>
                    )
                  } else if (item.chartType === 'card') {
                    return (
                      <Col span={item.width} key={item.uuid}>
                        <CardComponent
                          card={item}
                          config={config}
                          plotchange={this.updateconfig}
                        />
                      </Col>
                    )
                  } else {
                    return (
                      <Col span={item.width} key={item.uuid}>
                        <ChartComponent
                          plot={item}
                          config={config}
                          plotchange={this.updateconfig}
                        />
                      </Col>
                    )
                  }
                })}
              </div>
              {/* 标签组 */}
              <TabsComponent
                config={config}
                tabs={this.state.tabviews}
                pasteContent={this.state.pasteContent}
                usefulFields={this.props.permFuncField}
                setSubConfig={(_btn) => this.setSubConfig(_btn, 'button')}
                updateaction={this.updateaction}
                setSubConfig={(item) => this.setSubConfig(item, 'tab')}
                updatetabs={this.updatetabs}
              />
              {/* 显示列 */}
              <ColumnComponent
                config={config}
                menu={this.props.menu}
                sysRoles={this.props.sysRoles}
                pasteContent={this.state.pasteContent}
                updatecolumn={this.updatecolumn}
              />
              {/* 标签组 */}
              {config.tabgroups.map((groupId, index) => {
                return (
                  <div key={index} className="tab-list">
                    {index === 0 ? <Tooltip placement="bottomLeft" overlayClassName="middle" title="在左侧工具栏《标签页》中,选择对应类型的标签页拖至此处添加。">
                      <Icon type="question-circle" />
                    </Tooltip> : null}
                    {index !== (config.tabgroups.length - 1) ?
                      <Icon type="arrow-down" onClick={() => {this.handleGroup(index, 'down')}} /> : null
                    }
                    {index !== 0 ? <Icon type="arrow-up" onClick={() => {this.handleGroup(index, 'up')}} /> : null}
                    {index === 0 ? <Icon type="plus" onClick={this.addTabGroup} /> : null}
                    {index !== 0 ? <Icon type="delete" onClick={() => {this.delTabGroup(groupId)}} /> : null}
                    <TabDragElement
                      type="tabs"
                      groupId={groupId}
                      list={config[groupId]}
                      handleList={this.handleList}
                      handleMenu={this.handleTab}
                      deleteMenu={this.deleteElement}
                      doubleClickCard={(tab) => this.setSubConfig(tab, 'tab')}
                      placeholder={this.state.dict['header.form.tab.placeholder']}
                    />
                  </div>)
              })}
            </Card>
          </div>
        </DndProvider>
        {/* 标签编辑 */}
        <Modal
          title={this.state.dict['header.modal.tabs.edit']}
          visible={modaltype === 'tabs'}
          width={750}
          maskClosable={false}
          onOk={this.handleSubmit}
          onCancel={this.editModalCancel}
          destroyOnClose
        >
          <TabForm
            type="tabs"
            dict={this.state.dict}
            card={this.state.card}
            tabs={this.state.tabviews}
            formlist={this.state.formlist}
            inputSubmit={this.handleSubmit}
            wrappedComponentRef={(inst) => this.tabsFormRef = inst}
          />
        </Modal>
        {/* 设置全局配置及列表数据源 */}
        <Modal
          title={this.state.dict['model.edit']}
          visible={this.state.settingVisible}
          width={750}
          maskClosable={false}
          onCancel={() => { // 取消修改
            this.setState({
              settingVisible: false
            })
          }}
          footer={[
            <CreateInterface key="interface" dict={this.state.dict} ref="tableCreatInterface" trigger={this.tableCreatInterface}/>,
            <CreateFunc key="create" dict={this.state.dict} ref="tableCreatFunc" trigger={this.tableCreatFunc}/>,
            <Button key="cancel" onClick={() => { this.setState({ settingVisible: false }) }}>{this.state.dict['header.cancel']}</Button>,
            <Button key="confirm" type="primary" loading={this.state.sqlVerifing} onClick={this.settingSave}>{this.state.dict['model.confirm']}</Button>
          ]}
          destroyOnClose
        >
          <SettingForm
            dict={this.state.dict}
            menu={this.props.menu}
            inputSubmit={this.settingSave}
            data={config.setting}
            columns={config.columns}
            usefulFields={this.props.permFuncField}
            wrappedComponentRef={(inst) => this.settingRef = inst}
          />
        </Modal>
        {/* 返回时未保存提示 */}
        <Modal
          bodyStyle={{textAlign: 'center', color: '#000000', fontSize: '16px'}}
          closable={false}
@@ -2029,7 +1510,8 @@
const mapStateToProps = (state) => {
  return {
    sysRoles: state.sysRoles,
    permFuncField: state.permFuncField
    permFuncField: state.permFuncField,
    memberLevel: state.memberLevel
  }
}