king
2020-06-02 a181fc113d024ed34d6b488c65882961bd1de3f4
2020-06-02
22个文件已修改
12个文件已添加
4254 ■■■■■ 已修改文件
src/components/tabview/index.jsx 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/en-US/model.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/zh-CN/model.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/commontable/index.jsx 75 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/formtab/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/managetable/index.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtable/index.jsx 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/treepage/index.jsx 882 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/treepage/index.scss 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/cardcomponent/index.jsx 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/cardcomponent/index.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/index.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/index.jsx 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/formtabconfig/source.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/menuconfig/editthdmenu/index.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/chartgroupcomponent/chartform/index.jsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tabscomponent/index.jsx 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tabscomponent/tabform/index.jsx 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tabscomponent/tabform/index.scss 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/index.jsx 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/index.scss 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/settingform/customscript/index.jsx 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/settingform/customscript/index.scss 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/settingform/index.jsx 765 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/settingform/index.scss 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/treesettingcomponent/settingform/utils.jsx 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/subtableconfig/index.jsx 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/treepageconfig/index.jsx 921 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/treepageconfig/index.scss 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/treepageconfig/source.jsx 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/editcomponent/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/formconfig.jsx 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/option.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tabview/index.jsx
@@ -18,6 +18,7 @@
const Home = asyncComponent(() => import('@/tabviews/home'))
const CommonTable = asyncComponent(() => import('@/tabviews/commontable'))
const TreePage = asyncComponent(() => import('@/tabviews/treepage'))
const VerupTable = asyncComponent(() => import('@/tabviews/verupmanage'))
const ScriptTable = asyncComponent(() => import('@/tabviews/scriptmanage'))
const TabManage = asyncComponent(() => import('@/tabviews/tabmanage'))
@@ -102,6 +103,8 @@
      return (<Home MenuNo={view.MenuNo} MenuID={view.MenuID} MenuName={view.MenuName} key={view.MenuID}/>)
    } else if (view.type === 'CommonTable') {
      return (<CommonTable MenuNo={view.MenuNo} MenuID={view.MenuID} MenuName={view.MenuName} key={view.MenuID} param={view.param}/>)
    } else if (view.type === 'TreePage') {
      return (<TreePage MenuNo={view.MenuNo} MenuID={view.MenuID} MenuName={view.MenuName} key={view.MenuID}/>)
    } else if (view.type === 'VerupTable') {
      return (<VerupTable MenuNo={view.MenuNo} MenuID={view.MenuID} MenuName={view.MenuName} key={view.MenuID}/>)
    } else if (view.type === 'ScriptTable') {
@@ -210,7 +213,7 @@
                      className="test"
                      tab={
                        <span className="tab-control">
                          {['CommonTable', 'FormTab'].includes(view.type) ?
                          {['CommonTable', 'FormTab', 'TreePage'].includes(view.type) ?
                            <Icon type="redo" onClick={(e) => {this.refreshTabview(e, view)}}/> : null
                          }
                          <span className="tab-name" onClick={(e) => {this.changeTab(e, view)}}>
@@ -224,7 +227,7 @@
                      key={view.MenuID}
                    >
                      {this.selectcomponent(view)}
                      {view.type !== 'CommonTable' && view.type !== 'ManageTable' ?
                      {!['CommonTable', 'TreePage', 'ManageTable'].includes(view.type) ?
                        <Button
                          icon="copy"
                          shape="circle"
src/locales/en-US/model.js
@@ -82,6 +82,7 @@
  'header.form.refresh.view': 'Refresh the page',
  'header.form.refresh.grid': 'Refresh the table',
  'header.form.refresh.maingrid': '刷新主表',
  'header.form.refresh.mainline': '刷新主表(行)',
  'header.form.refresh.equaltab': '刷新同级标签',
  'header.form.refresh.subgrid': '刷新子表',
  'header.form.popClose': '标签关闭',
src/locales/zh-CN/model.js
@@ -82,6 +82,7 @@
  'header.form.refresh.view': '刷新页面',
  'header.form.refresh.grid': '刷新表格',
  'header.form.refresh.maingrid': '刷新主表',
  'header.form.refresh.mainline': '刷新主表(行)',
  'header.form.refresh.equaltab': '刷新同级标签',
  'header.form.refresh.subgrid': '刷新子表',
  'header.form.popClose': '标签关闭',
src/tabviews/commontable/index.jsx
@@ -173,6 +173,8 @@
      config.tabgroups.forEach(group => {
        group.sublist = group.sublist.filter(tab => permAction[tab.linkTab])
      })
      // 去除空行标签
      config.tabgroups = config.tabgroups.filter(group => group.sublist.length > 0)
      // 视图权限
      config.charts = config.charts.filter(item => {
@@ -255,7 +257,6 @@
      let _tabActive = {} // 筛选展开的tab页
      config.tabgroups.forEach(group => {
        if (group.sublist.length === 0) return
        _tabActive[group.uuid] = group.sublist[0].uuid
      })
@@ -928,9 +929,11 @@
  handleMainTable = (type, tab) => {
    if (type === 'maingrid' && tab.supMenu === 'mainTable') {
      this.reloadtable()
    } else if (type === 'maingrid' && tab.supMenu) {
    } else if (type === 'mainline' && tab.supMenu === 'mainTable') {
      this.reloadtable()
    } else if ((type === 'maingrid' || type === 'mainline') && tab.supMenu) {
      this.setState({
        refreshtabs: [tab.supMenu]
        refreshtabs: [type, tab.supMenu]
      }, () => {
        this.setState({
          refreshtabs: null
@@ -1445,41 +1448,37 @@
            })}
          </div> : null }
          {setting && setting.onload !== 'false' &&
            config.tabgroups.map(group => {
              if (group.sublist.length === 0) return null
              return (
                <Tabs activeKey={tabActive[group.uuid]} key={group.uuid} onChange={(key) => this.setState({tabActive: {...tabActive, [group.uuid]: key}})}>
                  {group.sublist.map(_tab => {
                    return (
                      <TabPane tab={
                        <span>
                          {_tab.icon ? <Icon type={_tab.icon} /> : null}
                          {_tab.label}
                        </span>
                      } key={_tab.uuid}>
                        {_tab.type === 'SubTable' ?
                          <SubTable
                            Tab={_tab}
                            menuType="main"
                            MenuID={_tab.linkTab}
                            mainSearch={_tab.searchPass === 'true' ? search : null}
                            userConfig={userConfig ? userConfig[_tab.uuid] : null}
                            triggerBtn={triggerBtn}
                            SupMenuID={this.props.MenuID}
                            refreshtabs={this.state.refreshtabs}
                            ContainerId={this.state.ContainerId}
                            BID={this.state.BIDs[_tab.supMenu] || ''}
                            BData={this.state.BIDs[_tab.supMenu + 'data'] || ''}
                            handleTableId={this.handleTableId}
                            handleMainTable={(type) => this.handleMainTable(type, _tab)}
                          /> : null}
                      </TabPane>
                    )
                  })}
                </Tabs>
              )
            })
            config.tabgroups.map(group => (
              <Tabs activeKey={tabActive[group.uuid]} key={group.uuid} onChange={(key) => this.setState({tabActive: {...tabActive, [group.uuid]: key}})}>
                {group.sublist.map(_tab => {
                  return (
                    <TabPane tab={
                      <span>
                        {_tab.icon ? <Icon type={_tab.icon} /> : null}
                        {_tab.label}
                      </span>
                    } key={_tab.uuid}>
                      {_tab.type === 'SubTable' ?
                        <SubTable
                          Tab={_tab}
                          menuType="main"
                          MenuID={_tab.linkTab}
                          mainSearch={_tab.searchPass === 'true' ? search : null}
                          userConfig={userConfig ? userConfig[_tab.uuid] : null}
                          triggerBtn={triggerBtn}
                          SupMenuID={this.props.MenuID}
                          refreshtabs={this.state.refreshtabs}
                          ContainerId={this.state.ContainerId}
                          BID={this.state.BIDs[_tab.supMenu] || ''}
                          BData={this.state.BIDs[_tab.supMenu + 'data'] || ''}
                          handleTableId={this.handleTableId}
                          handleMainTable={(type) => this.handleMainTable(type, _tab)}
                        /> : null}
                    </TabPane>
                  )
                })}
              </Tabs>)
            )
          }
          <Button
            icon="copy"
src/tabviews/formtab/index.jsx
@@ -570,11 +570,11 @@
   * @description 子表操作完成后刷新主表
   */
  handleMainTable = (type, tab) => {
    if (type === 'maingrid' && tab.supMenu === 'mainTable') {
    if ((type === 'maingrid' || type === 'mainline') && tab.supMenu === 'mainTable') {
      this.loadmaindata()
    } else if (type === 'maingrid' && tab.supMenu) {
    } else if ((type === 'maingrid' || type === 'mainline') && tab.supMenu) {
      this.setState({
        refreshtabs: [tab.supMenu]
        refreshtabs: [type, tab.supMenu]
      }, () => {
        this.setState({
          refreshtabs: null
src/tabviews/managetable/index.jsx
@@ -644,9 +644,11 @@
  handleMainTable = (type, tab) => {
    if (type === 'maingrid' && tab.supMenu === 'mainTable') {
      this.reloadtable()
    } else if (type === 'maingrid' && tab.supMenu) {
    } else if (type === 'mainline' && tab.supMenu === 'mainTable') {
      this.reloadtable()
    } else if ((type === 'maingrid' || type === 'mainline') && tab.supMenu) {
      this.setState({
        refreshtabs: [tab.supMenu]
        refreshtabs: [type, tab.supMenu]
      }, () => {
        this.setState({
          refreshtabs: null
src/tabviews/subtable/index.jsx
@@ -469,6 +469,7 @@
    let _BID = this.props.BID
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    let prex = this.props.Tab && this.props.Tab.label ? this.props.Tab.label + '-' : ''
    if (requireFields.length > 0) {
      let labels = requireFields.map(item => item.label)
@@ -476,7 +477,7 @@
      notification.warning({
        top: 92,
        message: this.state.dict['form.required.input'] + labels.join('、') + ' !',
        message: prex + this.state.dict['form.required.input'] + labels.join('、') + ' !',
        duration: 3
      })
      return
@@ -522,7 +523,6 @@
      this.setState({
        loading: false
      })
      let prex = this.props.Tab && this.props.Tab.label ? this.props.Tab.label + ': ' : ''
      notification.error({
        top: 92,
@@ -800,6 +800,8 @@
    } else if (btn.execSuccess === 'equaltab' && type === 'success') {
      this.reloadtable()
      this.props.handleMainTable('equaltab')
    }else if (btn.execSuccess === 'mainline' && type === 'success') {
      this.props.handleMainTable('mainline')
    }
  }
src/tabviews/treepage/index.jsx
New file
@@ -0,0 +1,882 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { notification, Spin, Tabs, Icon, Modal, Button, message, Tree, Typography, Row, Col, Card, Input, Empty } from 'antd'
import moment from 'moment'
import Api from '@/api'
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import asyncSpinComponent from '@/utils/asyncSpinComponent'
import {refreshTabView, modifyTabview} from '@/store/action'
import NotFount from '@/components/404'
import './index.scss'
const VerifyCard = asyncComponent(() => import('@/tabviews/zshare/verifycard'))
const SubTable = asyncSpinComponent(() => import('@/tabviews/subtable'))
const { TabPane } = Tabs
const { TreeNode } = Tree
const { Search } = Input
const { Paragraph } = Typography
class TreePage extends Component {
  static propTpyes = {
    MenuID: PropTypes.string,    // 菜单Id
    MenuNo: PropTypes.string,    // 菜单参数
    MenuName: PropTypes.string   // 菜单名称
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    ContainerId: Utils.getuuid(), // 菜单外层html Id
    loadingview: true,    // 页面加载中
    viewlost: false,      // 页面丢失:1、未获取到配置-页面丢失;2、页面未启用
    lostmsg: '',          // 页面丢失时的提示信息
    config: {},           // 页面配置信息,包括按钮、搜索、显示列、标签等
    userConfig: null,     // 用户自定义设置
    userParam: null,      // 保存用户编辑中的配置
    setting: null,        // 页面全局设置:数据源、按钮及显示列固定、主键等
    treedata: null,       // 列表数据集
    treeNodes: null,      // 列表数据集
    loading: false,       // 列表数据加载中
    BIDs: {},             // 上级表id
    visible: false,       // 弹框显示隐藏控制
    treevisible: false,   // 菜单结构树弹框显示隐藏控制
    refreshtabs: null,    // 需要刷新的标签集
    confirmLoading: false,// 自定义设置模态框加载中
    revertLoading: false, // 恢复默认设置
    settingVisible: false,// 自定义设置模态框
    triggerBtn: null,     // 点击表格中或快捷键触发的按钮
    tabActive: null,      // 标签页展开控制
  }
  /**
   * @description 获取页面配置信息
   */
  async loadconfig () {
    const { permAction } = this.props
    let _param = {
      func: 'sPC_Get_LongParam',
      MenuID: this.props.MenuID
    }
    let result = await Api.getSystemCacheConfig(_param)
    if (result.status) {
      let config = ''
      let userConfig = null
      let _curUserConfig = ''
      try { // 配置信息解析
        config = JSON.parse(window.decodeURIComponent(window.atob(result.LongParam)))
      } catch (e) {
        console.warn('Parse Failure')
        config = ''
      }
      if (result.LongParamUser) {
        try { // 配置信息解析
          userConfig = JSON.parse(window.decodeURIComponent(window.atob(result.LongParamUser)))
          _curUserConfig = userConfig[this.props.MenuID]
        } catch (e) {
          console.warn('Parse Failure')
          userConfig = null
        }
      }
      // 页面配置解析错误时提示
      if (!config) {
        this.setState({
          loadingview: false,
          viewlost: true
        })
        return
      }
      // 页面未启用时,显示未启用页面
      if (!config.enabled) {
        this.setState({
          loadingview: false,
          viewlost: true,
          lostmsg: this.state.dict['main.view.unenabled']
        })
        return
      }
      // 权限过滤
      config.tabgroups.forEach(group => {
        group.sublist = group.sublist.filter(tab => permAction[tab.linkTab])
      })
      config.tabgroups = config.tabgroups.filter(group => group.sublist.length > 0)
      if (_curUserConfig) {
        config.setting = {...config.setting, ..._curUserConfig.setting}
        config.easyCode = _curUserConfig.easyCode || config.easyCode || ''
      }
      let _tabActive = {} // 筛选展开的tab页
      let _tabgroups = []
      config.tabgroups.forEach(group => {
        let _group = fromJS(group).toJS()
        _group.sublist = _group.sublist.filter(tab => !tab.level)
        if (_group.sublist.length > 0) {
          _tabActive[_group.uuid] = _group.sublist[0].uuid
          _tabgroups.push(_group)
        }
      })
      this.setState({
        loadingview: false,
        config: config,
        tabActive: _tabActive,
        userConfig: userConfig,
        setting: config.setting,
        tabgroups: _tabgroups
      }, () => {
        this.loadmaindata()
        this.setShortcut()
      })
    } else {
      this.setState({
        loadingview: false,
        viewlost: true
      })
      notification.warning({
        top: 92,
        message: result.message,
        duration: 5
      })
    }
  }
  setShortcut = () => {
    const { userConfig, config } = this.state
    if (!userConfig) return
    document.onkeydown = (event) => {
      let e = event || window.event
      let keyCode = e.keyCode || e.which || e.charCode
      let preKey = ''
      if (e.ctrlKey) {
        preKey = 'ctrl'
      } else if (e.shiftKey) {
        preKey = 'shift'
      } else if (e.altKey) {
        preKey = 'alt'
      }
      if (!preKey) return
      let istrigger = false
      Object.keys(userConfig).forEach(key => {
        if (key === this.props.MenuID || !userConfig[key].action || istrigger) return
        let _actions = userConfig[key].action
        Object.keys(_actions).forEach(btnkey => {
          let item = _actions[btnkey]
          if (!item.shortcut || typeof(item.shortcut) !== 'object' || item.shortcut.length === 0 || istrigger) return
          if (preKey === item.shortcut[0] && keyCode === item.shortcut[1]) {
            e.preventDefault()
            istrigger = true
            let _groupId = ''
            let _ActiveTabId = ''
            config.tabgroups.forEach(group => {
              if (group.sublist.length === 0) return
              let _tab = group.sublist.filter(tab => tab.uuid === key)[0]
              if (_tab) {
                _groupId = group.uuid
                _ActiveTabId = _tab.uuid
              }
            })
            if (this.state.tabActive[_groupId] === _ActiveTabId) {
              this.setState({
                triggerBtn: {
                  uuid: new Date().getTime(),
                  parentId: key,
                  button: {...item, uuid: btnkey},
                  data: null
                }
              })
            } else {
              this.setState({
                tabActive: {...this.state.tabActive, [_groupId]: _ActiveTabId}
              }, () => {
                this.setState({
                  triggerBtn: {
                    uuid: new Date().getTime(),
                    parentId: key,
                    button: {...item, uuid: btnkey},
                    data: null
                  }
                })
              })
            }
          }
        })
      })
    }
  }
  /**
   * @description 主表数据加载
   */
  async loadmaindata () {
    const { setting, BIDs } = this.state
    let param = ''
    this.setState({
      loading: true
    })
    if (setting.interType !== 'inner' || (setting.interType === 'inner' && setting.innerFunc)) {
      param = this.getCustomParam()
    } else {
      param = this.getDefaultParam()
    }
    this.handleTableId('mainTable', '', '')
    if (!param) { // 未获取参数时,不发请求
      return
    }
    let result = await Api.genericInterface(param)
    if (result.status) {
      let parentNodes = []
      let _options = []
      result.data.forEach(item => {
        let pval = item[setting.parentField]
        let val = item[setting.valueField]
        if (!val) return
        if (pval === setting.mark) {
          parentNodes.push({
            title: item[setting.labelField] || '',
            key: val,
            parentId: '',
            level: 1
          })
        } else if (pval) {
          _options.push({
            title: item[setting.labelField] || '',
            key: val,
            parentId: pval
          })
        }
      })
      let _treedata = this.getTree(parentNodes, _options)
      this.setState({
        treedata: _treedata,
        treeNodes: fromJS(_treedata).toJS(),
        loading: false,
        BIDs: {
          ...BIDs,
          mainTable: ''
        }
      })
    } else {
      this.setState({
        loading: false,
        treeNodes: [],
        treedata: []
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  /**
   * @description 获取结构树信息
   */
  getTree = (parents, options) => {
    parents.forEach(parent => {
      parent.children = []
      // 添加菜单的子元素
      options = options.filter(option => {
        if (option.parentId === parent.key) {
          option.level = parent.level + 1
          parent.children.push(option)
          return false
        }
        return true
      })
      if (parent.children.length === 0) {
        parent.children = null
      } else {
        parent.children = this.getTree(parent.children, options)
      }
    })
    return parents
  }
  /**
   * @description 获取树节点
   */
  renderTreeNodes = (nodes) => {
    return nodes.map(item => {
      if (item.children) {
        return (
          <TreeNode icon={<Icon type="folder-open" />} title={item.title} key={item.key} dataRef={item}>
            {this.renderTreeNodes(item.children)}
          </TreeNode>
        )
      }
      return <TreeNode icon={<Icon type="file" />} key={item.key} title={item.title} dataRef={item} isLeaf />
    })
  }
  selectTreeNode = (selectedKeys, {selected, node}) => {
    const { config } = this.state
    let selectId = selectedKeys[0]
    let _data = fromJS(node.props.dataRef).toJS()
    delete _data.children
    let _tabgroups = []
    let _tabActive = {}
    config.tabgroups.forEach(group => {
      let _group = fromJS(group).toJS()
      _group.sublist = _group.sublist.filter(tab => !tab.level || (selected && tab.level === _data.level))
      if (_group.sublist.length > 0) {
        _tabActive[_group.uuid] = _group.sublist[0].uuid
        _tabgroups.push(_group)
      }
    })
    this.setState({
      tabgroups: _tabgroups,
      tabActive: _tabActive
    })
    if (selected) {
      this.handleTableId('mainTable', selectId, _data)
    } else {
      this.handleTableId('mainTable', '', '')
    }
  }
  /**
   * @description 获取用户自定义存储过程传参
   */
  getCustomParam = () => {
    const { setting } = this.state
    let param = {
      OrderCol: setting.order,
      dataM: this.props.dataManager ? 'Y' : '',
    }
    if (setting.interType === 'inner') {
      param.func = setting.innerFunc
    } else {
      if (setting.sysInterface === 'true' && window.GLOB.mainSystemApi) {
        param.rduri = window.GLOB.mainSystemApi
      } else if (setting.sysInterface !== 'true') {
        param.rduri = setting.interface
      }
      if (setting.outerFunc) {
        param.func = setting.outerFunc
      }
    }
    return param
  }
  /**
   * @description 获取系统存储过程 sPC_Get_TableData 的参数
   */
  getDefaultParam = () => {
    const { setting } = this.state
    let arr_field = `${setting.valueField},${setting.labelField},${setting.parentField}`
    let param = {
      func: 'sPC_Get_TableData',
      obj_name: 'data',
      arr_field: arr_field,
      custom_script: setting.customScript || '',
      default_sql: setting.default || 'true',
      dataM: this.props.dataManager ? 'Y' : ''
    }
    let _dataresource = setting.dataresource
    if (/\s/.test(_dataresource)) {
      _dataresource = '(' + _dataresource + ') tb'
    }
    if (this.props.dataManager) { // 数据权限
      _dataresource = _dataresource.replace(/\$@/ig, '/*')
      _dataresource = _dataresource.replace(/@\$/ig, '*/')
      param.custom_script = param.custom_script.replace(/\$@/ig, '/*')
      param.custom_script = param.custom_script.replace(/@\$/ig, '*/')
    } else {
      _dataresource = _dataresource.replace(/@\$|\$@/ig, '')
      param.custom_script = param.custom_script.replace(/@\$|\$@/ig, '')
    }
    let LText = ''
    if (setting.default !== 'false') {
      LText = `select ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${setting.order}) as rows from ${_dataresource}) tmptable order by tmptable.rows`
    }
    if (param.custom_script) {
      param.custom_script = param.custom_script.replace(/@orderBy@/ig, setting.order)
      if (LText) {
        LText += `
          aaa:
          if @ErrorCode!=''
            insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
        `
      } else {
        param.custom_script += `
          aaa:
          if @ErrorCode!=''
            insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
        `
      }
      param.custom_script = Utils.formatOptions(param.custom_script)
    }
    param.LText = Utils.formatOptions(LText)
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
    param.secretkey = Utils.encrypt(param.LText, param.timestamp)
    param.DateCount = ''
    return param
  }
  /**
   * @description 页面刷新,重新获取配置
   */
  reloadview = () => {
    this.setState({
      loadingview: true,
      viewlost: false,
      lostmsg: '',
      config: {},
      userConfig: null,
      userParam: null,
      setting: null,
      treedata: null,
      treeNodes: null,
      loading: false,
      BIDs: {},
    }, () => {
      this.loadconfig()
    })
  }
  /**
   * @description 表格刷新
   */
  reloadtable = () => {
    this.setState({
      pageIndex: 1
    }, () => {
      this.loadmaindata()
    })
  }
  /**
   * @description 子表操作完成后刷新主表
   */
  handleMainTable = (type, tab) => {
    if (type === 'maingrid' && tab.supMenu === 'mainTable') {
      this.reloadtable()
    } else if (type === 'mainline' && tab.supMenu === 'mainTable') {
      this.reloadtable()
    } else if ((type === 'maingrid' || type === 'mainline') && tab.supMenu) {
      this.setState({
        refreshtabs: [type, tab.supMenu]
      }, () => {
        this.setState({
          refreshtabs: null
        })
      })
    } else if (type === 'equaltab' && tab.equalTab && tab.equalTab.length > 0) {
      this.setState({
        refreshtabs: tab.equalTab
      }, () => {
        this.setState({
          refreshtabs: null
        })
      })
    }
  }
  /**
   * @description 表格Id变化
   */
  handleTableId = (type, id, data) => {
    const { BIDs } = this.state
    this.setState({
      BIDs: {
        ...BIDs,
        [type]: id,
        [type + 'data']: data
      }
    })
  }
  handleviewconfig = (e) => {
    e.stopPropagation()
    const { MenuNo } = this.props
    const { config } = this.state
    if (config && config.funcs && config.funcs.length > 0) {
      this.setState({
        treevisible: true
      })
    } else {
      let oInput = document.createElement('input')
      oInput.value = MenuNo || ''
      document.body.appendChild(oInput)
      oInput.select()
      document.execCommand('Copy')
      document.body.removeChild(oInput)
      message.success(this.state.dict['main.copy.success'])
    }
  }
  getTreeNode = (data) => {
    let _type = {
      view: '页面',
      btn: '按钮',
      tab: '标签'
    }
    return data.map(item => {
      let _title = _type[item.subtype]
      let _others = []
      _others.push(
        (item.menuNo ? item.menuNo + '(菜单参数)' : ''),
        (item.tableName ? item.tableName + '(表名) ' : ''),
        (item.innerFunc ? item.innerFunc + '(内部函数) ' : ''),
        (item.outerFunc ? item.outerFunc + '(外部函数)' : '')
      )
      _others = _others.filter(Boolean)
      _others = _others.join('、')
      if (item.label) {
        _title = _title + '(' + item.label + ')'
      }
      if (_others) {
        _title = _title + ': ' + _others
      }
      if (item.subfuncs && item.subfuncs.length > 0) {
        return (
          <TreeNode title={_title} key={item.uuid} dataRef={item} selectable={false}>
            {this.getTreeNode(item.subfuncs)}
          </TreeNode>
        )
      }
      return <TreeNode key={item.uuid} title={_title} isLeaf selectable={false} />
    })
  }
  controlCustomSetting = () => {
    this.setState({
      settingVisible: true,
      confirmLoading: false,
      revertLoading: false
    })
  }
  changeMenuParam = (param) => {
    this.setState({userParam: param})
  }
  settingRevert = () => {
    let param = {
      func: 's_TrdMenu_UserParam_del',
      MenuID: this.props.MenuID
    }
    this.setState({
      revertLoading: true
    })
    Api.getSystemConfig(param).then(result => {
      if (!result.status) {
        this.setState({
          revertLoading: false
        })
        notification.warning({
          top: 92,
          message: result.message,
          duration: 5
        })
        return
      }
      this.setState({
        settingVisible: false,
        revertLoading: false
      }, () => {
        window.GLOB.CacheMap = new Map()
        this.reloadview()
      })
    })
  }
  settingSubmit = () => {
    const { userParam } = this.state
    let _LongParam = ''
    try {
      _LongParam = window.btoa(window.encodeURIComponent(JSON.stringify(userParam)))
    } catch (e) {
      notification.warning({
        top: 92,
        message: '编译错误',
        duration: 5
      })
      return
    }
    let easyCode = userParam[this.props.MenuID] ? userParam[this.props.MenuID].easyCode : ''
    let param = {
      func: 'sPC_TrdMenu_UserParam',
      MenuID: this.props.MenuID,
      EasyCode: easyCode || '',
      LongParam: _LongParam
    }
    this.setState({
      confirmLoading: true
    })
    Api.getSystemConfig(param).then(result => {
      if (!result.status) {
        this.setState({
          confirmLoading: false
        })
        notification.warning({
          top: 92,
          message: result.message,
          duration: 5
        })
        return
      }
      this.setState({
        settingVisible: false,
        confirmLoading: false
      }, () => {
        window.GLOB.CacheMap = new Map()
        this.reloadview()
      })
    })
  }
  UNSAFE_componentWillMount () {
    // 组件加载时,获取菜单数据
    this.loadconfig()
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (nextProps.refreshTab && nextProps.refreshTab.MenuID === this.props.MenuID) {
      if (nextProps.refreshTab.position === 'view') {
        this.reloadview()
      }
      this.props.refreshTabView('')
    } else if (!is(fromJS(this.props.tabviews), fromJS(nextProps.tabviews))) {
      let selectTab = nextProps.tabviews.filter(tab => tab.selected)[0]
      if (selectTab && selectTab.MenuID === this.props.MenuID) {
        this.setShortcut()
      }
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    document.onkeydown = () => {}
  }
  render() {
    const { setting, loadingview, viewlost, config, triggerBtn, userConfig, tabActive, tabgroups, treeNodes, treedata } = this.state
    return (
      <div className="tree-page" id={this.state.ContainerId}>
        {loadingview && <Spin size="large" />}
        {setting ? <Row gutter={16}>
          <Col span={setting.width}>
            <Card
              className="tree-card"
              title={
                <span className="tree-title">
                  <span className="title">{setting.title}</span>
                  {setting.searchable !== 'false' ? <Search onSearch={this.treeFilter} /> : null}
                </span>
              }
              bordered={false}
            >
              {treeNodes && treeNodes.length > 0 ? <div className="tree-box">
                <Tree
                  blockNode
                  onSelect={this.selectTreeNode}
                  showIcon={setting.showIcon === 'true'}
                  showLine={setting.showLine === 'true'}
                >
                  {this.renderTreeNodes(treeNodes)}
                </Tree>
              </div> : null}
              {treeNodes && treeNodes.length === 0 ? <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} /> : null}
              {!treedata ? <Spin /> : null}
            </Card>
          </Col>
          <Col span={24 - setting.width} style={{paddingTop: 6}}>
            {tabgroups.map(group => (
              <Tabs activeKey={tabActive[group.uuid]} key={group.uuid} onChange={(key) => this.setState({tabActive: {...tabActive, [group.uuid]: key}})}>
                {group.sublist.map(_tab => {
                  return (
                    <TabPane tab={
                      <span>
                        {_tab.icon ? <Icon type={_tab.icon} /> : null}
                        {_tab.label}
                      </span>
                    } key={_tab.uuid}>
                      {_tab.type === 'SubTable' ?
                        <SubTable
                          Tab={_tab}
                          menuType="main"
                          MenuID={_tab.linkTab}
                          mainSearch={null}
                          userConfig={userConfig ? userConfig[_tab.uuid] : null}
                          triggerBtn={triggerBtn}
                          SupMenuID={this.props.MenuID}
                          refreshtabs={this.state.refreshtabs}
                          ContainerId={this.state.ContainerId}
                          BID={this.state.BIDs[_tab.supMenu] || ''}
                          BData={this.state.BIDs[_tab.supMenu + 'data'] || ''}
                          handleTableId={this.handleTableId}
                          handleMainTable={(type) => this.handleMainTable(type, _tab)}
                        /> : null}
                    </TabPane>
                  )
                })}
              </Tabs>)
            )}
          </Col>
        </Row> : null}
        <Button
          icon="copy"
          shape="circle"
          className="tree-page-copy"
          onClick={this.handleviewconfig}
        />
        <Modal
          className="menu-tree-modal"
          title={'菜单结构树'}
          width={'650px'}
          maskClosable={false}
          visible={this.state.treevisible}
          onCancel={() => this.setState({treevisible: false})}
          footer={[
            <Button key="close" onClick={() => this.setState({treevisible: false})}>{this.state.dict['main.close']}</Button>
          ]}
          destroyOnClose
        >
          <div className="menu-header">
            <span>菜单名称:{this.props.MenuName}</span>
            <span>菜单参数:{<Paragraph copyable>{this.props.MenuNo}</Paragraph>}</span>
          </div>
          {this.state.treevisible ? <Tree defaultExpandAll showLine={true}>
            {this.getTreeNode(config.funcs)}
          </Tree> : null}
        </Modal>
        {/* 按钮使用系统存储过程时,验证信息模态框 */}
        <Modal
          wrapClassName="common-table-custom-modal"
          title={'自定义设置'}
          maskClosable={false}
          width={950}
          visible={this.state.settingVisible}
          onCancel={() => { this.setState({ settingVisible: false }) }}
          footer={[
            <Button key="revert" type="danger" loading={this.state.revertLoading} onClick={this.settingRevert}>{this.state.dict['main.revert.default']}</Button>,
            <Button key="cancel" onClick={() => { this.setState({ settingVisible: false }) }}>{this.state.dict['main.cancel']}</Button>,
            <Button key="confirm" type="primary" loading={this.state.confirmLoading} onClick={this.settingSubmit}>{this.state.dict['main.submit']}</Button>
          ]}
          destroyOnClose
        >
          {this.state.settingVisible ?
            <VerifyCard
              MenuID={this.props.MenuID}
              MenuName={this.props.MenuName}
              permAction={this.props.permAction}
              permRoles={this.props.permRoles}
              config={this.state.config}
              userConfig={this.state.userConfig}
              columns={[]}
              handleParam={this.changeMenuParam}
            /> : null
          }
        </Modal>
        {viewlost ? <NotFount msg={this.state.lostmsg} /> : null}
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    tabviews: state.tabviews,
    refreshTab: state.refreshTab,
    permAction: state.permAction,
    permMenus: state.permMenus,
    permRoles: state.permRoles,
    memberLevel: state.memberLevel,
    dataManager: state.dataManager
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    refreshTabView: (refreshTab) => dispatch(refreshTabView(refreshTab)),
    modifyTabview: (tabviews) => dispatch(modifyTabview(tabviews))
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(TreePage)
src/tabviews/treepage/index.scss
New file
@@ -0,0 +1,168 @@
.tree-page {
  position: relative;
  min-height: calc(100vh - 94px);
  padding: 16px 16px 80px;
  .box404 {
    padding-top: 30px;
  }
  .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);
    }
  }
  > .ant-spin {
    position: absolute;
    z-index: 10;
    left: calc(50% - 22px);
    top: calc(50vh - 70px);
  }
  .tree-card {
    min-height: calc(100vh - 125px);
    box-shadow: 0px 0px 2px #dddddd;
    .ant-card-head {
      padding: 0 10px;
      .ant-card-head-title {
        padding: 13px 0 7px;
        .tree-title {
          display: inline-block;
          width: 100%;
          color: #1890ff;
          .ant-input-affix-wrapper {
            width: calc(100% - 140px);
            margin-top: 0px;
            float: right;
            height: 28px;
            .ant-input {
              max-width: 130px;
              border-radius: 20px;
              height: 30px;
            }
            .ant-input-suffix {
              margin-top: 3px;
            }
          }
          .title {
            display: inline-block;
            max-width: 50%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
          }
        }
      }
    }
    >.ant-card-body {
      padding: 0px;
      .ant-menu-inline {
        border-right: 0;
        margin-top: 2px;
      }
      .tree-box {
        min-height: calc(100vh - 180px);
        overflow-x: auto;
        padding-bottom: 20px;
      }
      .tree-box::-webkit-scrollbar {
        height: 10px;
      }
      .tree-box::-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);
      }
      .tree-box::-webkit-scrollbar-track {/*滚动条里面轨道*/
        box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
        border-radius: 3px;
        border: 1px solid rgba(0, 0, 0, 0.07);
        background: rgba(0, 0, 0, 0);
      }
      .ant-tree-node-selected
      .ant-tree.ant-tree-directory > li span.ant-tree-node-content-wrapper::before,
      .ant-tree.ant-tree-directory .ant-tree-child-tree > li span.ant-tree-node-content-wrapper::before {
        right: 0px;
      }
      >.ant-spin {
        margin: 10px;
      }
    }
  }
  > .ant-tabs {
    padding: 0px 20px;
    margin-bottom: 20px;
    .ant-tabs-tab:not(.ant-tabs-tab-active) {
      cursor: pointer;
    }
    .ant-tabs-tab.ant-tabs-tab-active {
      cursor: default;
    }
  }
  .tree-page-copy {
    position: fixed;
    z-index: 2;
    bottom: 65px;
    right: 30px;
    width: 40px;
    height: 40px;
  }
}
.menu-tree-modal {
  .ant-modal-body {
    min-height: 300px;
    .menu-header {
      text-align: center;
      span {
        font-weight: 600;
        margin-right: 20px;
      }
      .ant-typography {
        font-weight: 600;
        display: inline-block;
      }
    }
    .ant-tree li .ant-tree-node-content-wrapper {
      cursor: default;
    }
  }
}
.common-table-custom-modal {
  .ant-modal {
    top: 50px;
    padding-bottom: 5px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
      min-height: 250px;
      overflow-y: auto;
      .ant-empty {
        margin: 15vh 8px;
      }
    }
    .ant-modal-body::-webkit-scrollbar {
      width: 7px;
    }
    .ant-modal-body::-webkit-scrollbar-thumb {
      border-radius: 5px;
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
      background: rgba(0, 0, 0, 0.13);
    }
    .ant-modal-body::-webkit-scrollbar-track {
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
      border-radius: 3px;
      border: 1px solid rgba(0, 0, 0, 0.07);
      background: rgba(0, 0, 0, 0);
    }
  }
}
src/tabviews/zshare/cardcomponent/index.jsx
@@ -295,7 +295,7 @@
              {card.details.map((detail, i) => {
                return (
                  <div className={detail.class} key={i}>
                    {this.getContent(detail.column)}
                    {detail.column ? this.getContent(detail.column) : detail.content}
                  </div>
                )
              })}
@@ -538,6 +538,7 @@
          </Tabs> : null
        }
        {!data || data.length === 0 ? <Empty description={false}/> : null}
        <div className="clear"></div>
      </div>
    )
  }
src/tabviews/zshare/cardcomponent/index.scss
@@ -1,6 +1,7 @@
.card-row-box {
  min-height: 50px;
  padding: 0px 10px;
  margin-bottom: 15px;
  .chart-card-box {
    padding: 0 10px;
@@ -218,4 +219,7 @@
      }
    }
  }
  .clear {
    clear: both;
  }
}
src/templates/comtableconfig/index.jsx
@@ -26,7 +26,6 @@
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'))
@@ -44,13 +43,12 @@
  }
  state = {
    dict: CommonDict,        // 字典
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    config: null,            // 页面配置
    tableFields: [],         // 表格显示列
    fields: null,            // 搜索条件及显示列,可选字段
    menuformlist: null,      // 基本信息表单字段
    formlist: null,          // 搜索条件、按钮、显示列表单字段
    card: null,              // 编辑元素
    menuloading: false,      // 菜单保存中
    menucloseloading: false, // 菜单关闭时,选择保存
    loading: false,          // 加载中,页面spin
@@ -254,7 +252,7 @@
  }
  /**
   * @description 三级菜单切换模板
   * @description 三级菜单切换模板(弃用)
   */
  changeTemplate = () => {
    this.props.handleView({tabview: 'template'})
@@ -1371,7 +1369,7 @@
              <div>
                <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.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>
@@ -1451,7 +1449,6 @@
              </div>
              {/* 标签组 */}
              <TabsComponent
                type="main"
                config={config}
                tabs={this.state.tabviews}
                setSubConfig={(item) => this.setSubConfig(item, 'tab')}
src/templates/formtabconfig/index.jsx
@@ -146,6 +146,9 @@
      columns = menu.LongParam.columns
    }
    // 配置默认值,兼容
    _config.Template = 'FormTab'
    this.setState({
      config: _config,
      activeKey: btnTab.activeKey || '0',
@@ -2003,7 +2006,6 @@
              </div>
              {/* 标签组 */}
              <TabsComponent
                type="formtab"
                config={config}
                tabs={this.state.tabviews}
                setSubConfig={(item) => this.setSubConfig(item, 'tab')}
src/templates/formtabconfig/source.jsx
@@ -6,7 +6,7 @@
class CommonTableBaseData {
  baseConfig = {
    type: 'FormTab',
    Template: 'FormTab',
    enabled: false,
    setting: {
      datatype: 'query',
src/templates/menuconfig/editthdmenu/index.jsx
@@ -21,6 +21,7 @@
import './index.scss'
const ComTableConfig = asyncLoadComponent(() => import('@/templates/comtableconfig'))
const TreePageConfig = asyncLoadComponent(() => import('@/templates/treepageconfig'))
const FormTabConfig = asyncLoadComponent(() => import('@/templates/formtabconfig'))
const ModalConfig = asyncLoadComponent(() => import('@/templates/modalconfig'))
const SubTable = asyncLoadComponent(() => import('@/templates/subtableconfig'))
@@ -790,7 +791,7 @@
                <TabPane tab="系统模板" key="1">
                  <Row>
                    {this.state.sysTemplates.map((template, index) => {
                      if (template.hidden) return ''
                      if (template.hidden) return null
                      return (
                        <Col key={`${index}`} span={8}>
@@ -836,6 +837,14 @@
            </div>
          </div> : null
        }
        {this.state.tabview === 'TreePage' ?
          <TreePageConfig
            menu={this.state.editMenu}
            optionLibs={this.state.optionLibs}
            reloadmenu={() => {this.props.reload()}}
            handleView={this.handleView}
          /> : null
        }
        {this.state.tabview === 'CommonTable' ?
          <ComTableConfig
            menu={this.state.editMenu}
src/templates/sharecomponent/chartgroupcomponent/chartform/index.jsx
@@ -16,7 +16,7 @@
import card1 from '@/assets/img/card1.png'
import card2 from '@/assets/img/card2.png'
import card3 from '@/assets/img/card3.png'
import card4 from '@/assets/img/card4.png'
// import card4 from '@/assets/img/card4.png'
import card5 from '@/assets/img/card5.png'
import './index.scss'
@@ -125,11 +125,11 @@
      url: card3,
      subelement: ['content', 'avatar', 'header']
    },
    {
      uuid: 'card4',
      url: card4,
      subelement: ['content', 'avatar', 'bottom']
    },
    // {
    //   uuid: 'card4',
    //   url: card4,
    //   subelement: ['content', 'avatar', 'bottom']
    // },
    {
      uuid: 'card5',
      url: card5,
src/templates/sharecomponent/tabscomponent/index.jsx
@@ -16,7 +16,6 @@
class TablesComponent extends Component {
  static propTpyes = {
    type: PropTypes.string,       // 菜单类型
    tabs: PropTypes.array,        // 标签组
    config: PropTypes.object,     // 页面配置
    setSubConfig: PropTypes.func, // 子标签配置
@@ -28,6 +27,7 @@
    tabgroups: [],     // 标签组
    card: [],          // 编辑标签
    group: [],         // 编辑组
    levels: null,      // 树形页面的标签级别关联
    visible: false     // 模态框控制
  }
@@ -83,22 +83,28 @@
   * @description 标签编辑,筛选可选的下级标签与已关联的下级标签
   */
  handleTab = (card, _group) => {
    const { config } = this.props
    let tabgroups = fromJS(this.state.tabgroups).toJS()
    let menus = [
      {value: '', text: '空'},
      {value: 'mainTable', text: this.props.type === 'main' ? '主表' : '主数据'}
      {value: 'mainTable', text: '主表'}
    ]
    let equalTabs = []
    let supMenu = card.supMenu || ''
    let equalTab = card.equalTab || []
    let isuptab = true
    let equalTabIds = []
    let levels = {}
    tabgroups.forEach((group, i) => {
    tabgroups.forEach(group => {
      if (group.uuid === _group.uuid) {
        isuptab = false
        group.sublist.forEach(tab => { // 可关联的同级标签
          if (tab.level) {
            levels[tab.uuid] = tab.level
          }
          if (tab.uuid === card.uuid) return
          equalTabIds.push(tab.uuid)
@@ -106,6 +112,10 @@
        })
      } else if (isuptab) {
        group.sublist.forEach(tab => {
          if (tab.level) {
            levels[tab.uuid] = tab.level
          }
          menus.push({
            value: tab.uuid,
            text: tab.label
@@ -122,11 +132,16 @@
      equalTab = equalTab.filter(tabId => equalTabIds.includes(tabId))
    }
    if (config.Template !== 'TreePage') {
      levels = null
    }
    this.setState({
      visible: true,
      card: card,
      levels: levels,
      group: _group,
      formlist: getTabForm(card, supMenu, menus, equalTab, equalTabs, this.props.type)
      formlist: getTabForm(card, supMenu, menus, equalTab, equalTabs, config.Template)
    })
  }
@@ -176,6 +191,20 @@
        return _group
      })
      // 更新与之关联的标签显示级别
      if (config.Template === 'TreePage' && res.level !== card.level) {
        tabgroups = tabgroups.map(_group => {
          _group.sublist = _group.sublist.map(item => {
            if (item.supMenu === res.uuid) {
              item.level = res.level
            }
            return item
          })
          return _group
        })
      }
      this.setState({
        card: null,
        group: null,
@@ -204,6 +233,17 @@
          if (_group.uuid === group.uuid) {
            _group.sublist = _group.sublist.filter(item => item.uuid !== card.uuid)
          }
          return _group
        })
        // 清除与之关联标签的上级标签设置
        tabgroups = tabgroups.map(_group => {
          _group.sublist = _group.sublist.map(item => {
            if (item.supMenu === card.uuid) {
              item.supMenu = ''
            }
            return item
          })
          return _group
        })
@@ -386,6 +426,7 @@
            dict={this.state.dict}
            card={this.state.card}
            tabs={this.props.tabs}
            levels={this.state.levels}
            formlist={this.state.formlist}
            inputSubmit={this.handleSubmit}
            wrappedComponentRef={(inst) => this.tabsFormRef = inst}
src/templates/sharecomponent/tabscomponent/tabform/index.jsx
@@ -1,6 +1,6 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, Icon, Tooltip, Radio } from 'antd'
import { Form, Row, Col, Input, Select, Icon, Tooltip, Radio, InputNumber } from 'antd'
import { formRule } from '@/utils/option.js'
import Utils from '@/utils/utils.js'
import './index.scss'
@@ -11,6 +11,7 @@
    tabs: PropTypes.array,       // 可关联标签集
    dict: PropTypes.object,      // 字典项
    formlist: PropTypes.any,     // 表单
    levels: PropTypes.any,       // 标签显示级别
    card: PropTypes.object,      // 标签页信息
    inputSubmit: PropTypes.any   // 回车提交事件
  }
@@ -79,20 +80,19 @@
              ..._tabs
            ]
            item.initVal = ''
            item.hidden = true
          }
          return item
        })
      }, () => {
        this.setState({
          formlist: this.state.formlist.map(item => {
            if (item.key === 'linkTab') {
              item.hidden = false
            }
            return item
          })
        })
        if (this.props.form.getFieldValue('linkTab') !== undefined) {
          this.props.form.setFieldsValue({linkTab: ''})
        }
      })
    } else if (key === 'supMenu' && this.props.levels) {
      if (value && value !== 'mainTable') {
        this.props.form.setFieldsValue({level: this.props.levels[value]})
      }
    }
  }
@@ -111,7 +111,7 @@
    this.state.formlist.forEach((item, index) => {
      if (item.hidden || item.forbid) return
      if (item.type === 'text') { // 文本搜索
      if (item.type === 'text') {
        let rules = []
        if (item.key === 'foreignKey') {
@@ -143,6 +143,28 @@
                  ...rules
                ]
              })(<Input placeholder="" autoComplete="off" disabled={item.readonly} onPressEnter={this.handleSubmit} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'number') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={
              item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<InputNumber disabled={item.readonly} min={item.min} max={item.max} precision={0} />)}
            </Form.Item>
          </Col>
        )
@@ -269,7 +291,7 @@
      }
    }
    return (
      <Form {...formItemLayout} className="ant-advanced-search-form commontable-tab-form">
      <Form {...formItemLayout} className="model-tab-form">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
src/templates/sharecomponent/tabscomponent/tabform/index.scss
@@ -1,7 +1,16 @@
.ant-advanced-search-form.commontable-tab-form {
.model-tab-form {
  min-height: 180px;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
  .ant-input-number {
    width: 100%;
    .ant-input-number-input:read-only {
      color: red;
      :hover {
        border-color: #d9d9d9;
      }
    }
  }
}
src/templates/sharecomponent/treesettingcomponent/index.jsx
New file
@@ -0,0 +1,181 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Icon, Modal, Button, notification } from 'antd'
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import { getTreeSettingForm } from '@/templates/zshare/formconfig'
import SettingForm from './settingform'
import CreateFunc from '@/templates/zshare/createfunc'
import CreateInterface from '@/templates/zshare/createinterface'
import './index.scss'
class TreeSettingComponent extends Component {
  static propTpyes = {
    MenuID: PropTypes.string,        // 菜单ID
    config: PropTypes.object,        // 菜单配置信息
    permFuncField: PropTypes.array,  // 存储过程可用开头字段
    menuformRef: PropTypes.any,      // 菜单基本信息表单
    updatesetting: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    menu: null,          // 菜单信息
    formlist: null,      // 表单信息
    visible: false,      // 模态框控制
    loading: false       // 设置信息验证保存中
  }
  /**
   * @description 全局设置触发
   */
  changeSetting = () => {
    const { menuformRef, MenuID, config, permFuncField } = this.props
    let menu = {MenuID: MenuID}
    if (menuformRef) {
      menu = {MenuID: MenuID, MenuName: menuformRef.props.form.getFieldValue('MenuName') || '', MenuNo: menuformRef.props.form.getFieldValue('MenuNo') || ''}
    }
    let _config = fromJS(config).toJS()
    this.setState({
      visible: true,
      formlist: getTreeSettingForm(config.setting, permFuncField, MenuID),
      menu: menu,
      config: _config
    })
  }
  /**
   * @description 保存页面配置信息
   */
  settingSave = () => {
    const { config } = this.props
    this.setState({
      loading: true
    })
    this.settingRef.handleConfirm().then(res => {
      this.setState({
        visible: false,
        loading: false
      })
      this.props.updatesetting({...config, setting: res})
    }, () => {
      this.setState({
        loading: false
      })
    })
  }
  /**
   * @description 创建表格存储过程
   */
  tableCreatFunc = () => {
    const { config } = this.props
    const { menu } = 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.funcCreatComponent.exec(setting.innerFunc, newLText, DelText).then(result => {
        if (result === 'success') {
          this.props.updatesetting(_config)
        }
      })
    })
  }
  /**
   * @description 创建表格接口(读出)
   */
  tableCreatInterface = () => {
    const { config } = this.props
    const { menu } = 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 _menu = {
        type: 'main',
        MenuID: menu.MenuID,
        menuName: menu.MenuName,
        menuNo: menu.MenuNo
      }
      this.refs.tableCreatInterface.triggerOutInterface(_menu, _config)
    })
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  render() {
    const { dict, visible, config } = this.state
    return (
      <div className="model-tree-menu-setting">
        <Icon type="setting" onClick={this.changeSetting} />
        {/* 设置全局配置及列表数据源 */}
        <Modal
          wrapClassName="model-tree-setting-verify-modal"
          title={dict['model.edit']}
          visible={visible}
          width={900}
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false })}}
          footer={[
            <CreateInterface key="interface" dict={dict} ref="tableCreatInterface" trigger={this.tableCreatInterface}/>,
            <CreateFunc key="create" dict={dict} ref="funcCreatComponent" trigger={this.tableCreatFunc}/>,
            <Button key="cancel" onClick={() => { this.setState({ visible: false }) }}>{this.state.dict['header.cancel']}</Button>,
            <Button key="confirm" type="primary" loading={this.state.loading} onClick={this.settingSave}>{this.state.dict['model.confirm']}</Button>
          ]}
          destroyOnClose
        >
          <SettingForm
            dict={dict}
            config={config}
            menu={this.state.menu}
            inputSubmit={this.settingSave}
            formlist={this.state.formlist}
            wrappedComponentRef={(inst) => this.settingRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default TreeSettingComponent
src/templates/sharecomponent/treesettingcomponent/index.scss
New file
@@ -0,0 +1,42 @@
.model-tree-menu-setting {
  > .anticon-setting {
    position: absolute;
    font-size: 18px;
    right: 7px;
    top: 5px;
    padding: 10px;
    z-index: 1;
    color: #1890ff;
  }
}
.model-tree-setting-verify-modal {
  .ant-modal {
    top: 50px;
    padding-bottom: 5px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
      overflow-y: auto;
    }
    .ant-modal-body::-webkit-scrollbar {
      width: 7px;
    }
    .ant-modal-body::-webkit-scrollbar-thumb {
      border-radius: 5px;
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
      background: rgba(0, 0, 0, 0.13);
    }
    .ant-modal-body::-webkit-scrollbar-track {
      box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
      border-radius: 3px;
      border: 1px solid rgba(0, 0, 0, 0.07);
      background: rgba(0, 0, 0, 0);
    }
    .ant-empty-normal {
      margin: 5px 0px;
    }
    .ant-input-number {
      width: 100%;
    }
  }
}
src/templates/sharecomponent/treesettingcomponent/settingform/customscript/index.jsx
New file
@@ -0,0 +1,250 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Button, notification, Modal, Select } from 'antd'
import moment from 'moment'
import Utils from '@/utils/utils.js'
import SettingUtils from '../utils.jsx'
import Api from '@/api'
import './index.scss'
const { TextArea } = Input
class CustomForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,         // 字典项
    setting: PropTypes.object,      // 设置
    systemScripts: PropTypes.array, // 系统脚本
    scriptsChange: PropTypes.func   // 表单
  }
  state = {
    editItem: null,
    loading: false,
  }
  UNSAFE_componentWillMount() {
  }
  edit = (record) => {
    this.setState({
      editItem: record
    })
    this.props.form.setFieldsValue({
      sql: record.sql
    })
  }
  handleCancel = () => {
    this.setState({
      editItem: null
    })
    this.props.form.setFieldsValue({
      sql: ''
    })
  }
  handleConfirm = () => {
    const { setting } = this.props
    // 表单提交时检查输入值是否正确
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        values.uuid = this.state.editItem ? this.state.editItem.uuid : ''
        let _quot = values.sql.match(/'{1}/g)
        let _lparen = values.sql.match(/\({1}/g)
        let _rparen = values.sql.match(/\){1}/g)
        _quot = _quot ? _quot.length : 0
        _lparen = _lparen ? _lparen.length : 0
        _rparen = _rparen ? _rparen.length : 0
        if (_quot % 2 !== 0) {
          notification.warning({
            top: 92,
            message: 'sql中\'必须成对出现',
            duration: 5
          })
          return
        } else if (_lparen !== _rparen) {
          notification.warning({
            top: 92,
            message: 'sql中()必须成对出现',
            duration: 5
          })
          return
        } else if (/--/ig.test(values.sql)) {
          notification.warning({
            top: 92,
            message: '自定义sql语句中,不可出现字符 -- ,注释请用 /*内容*/',
            duration: 5
          })
          return
        }
        let error = Utils.verifySql(values.sql, 'customscript')
        if (error) {
          notification.warning({
            top: 92,
            message: 'sql中不可使用' + error,
            duration: 5
          })
          return
        }
        let _customScript = ''
        setting.scripts && setting.scripts.forEach(script => {
          if (this.state.editItem && this.state.editItem.uuid === script.uuid) {
            _customScript += `
            ${values.sql}
            `
          } else if (script.status !== 'false') {
            _customScript += `
            ${script.sql}
            `
          }
        })
        if (!this.state.editItem) {
          _customScript += `
            ${values.sql}
            `
        }
        if (_customScript) {
          _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000) select @ErrorCode='',@retmsg =''
            ${_customScript}
          `
        }
        let _setting = {...setting, customScript: _customScript}
        let param = {
          func: 's_debug_sql',
          LText: SettingUtils.getDebugSql(_setting)
        }
        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)
        this.setState({loading: true})
        Api.getLocalConfig(param).then(res => {
          if (res.status) {
            this.setState({
              loading: false,
              editItem: null
            }, () => {
              this.props.scriptsChange(values)
            })
            this.props.form.setFieldsValue({
              sql: ''
            })
          } else {
            this.setState({loading: false})
            Modal.error({
              title: res.message
            })
          }
        })
      }
    })
  }
  selectScript = (value, option) => {
    let _sql = this.props.form.getFieldValue('sql')
    if (_sql) {
      _sql = _sql + `
      `
    }
    _sql = _sql.replace(/\s{6}$/, '')
    _sql = _sql + `/*${option.props.children}*/
    `
    _sql = _sql.replace(/\s{4}$/, '')
    _sql = _sql + value
    this.props.form.setFieldsValue({
      sql: _sql
    })
  }
  render() {
    const { systemScripts, setting } = this.props
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="modal-tree-menu-setting-script">
        <Row gutter={24}>
          {setting.tableName ? <Col span={8}>
            <Form.Item label={'表名'} style={{whiteSpace: 'nowrap', margin: 0}}>
              {setting.tableName}
            </Form.Item>
          </Col> : null}
          <Col span={16}>
            <Form.Item label={'报错字段'} style={{margin: 0}}>
              ErrorCode, retmsg
            </Form.Item>
          </Col>
          <Col span={24} className="sqlfield">
            <Form.Item label={'可用字段'}>
              id, bid, loginuid, sessionuid, userid, appkey, time_id, orderBy, {setting.valueField}, {setting.labelField}, {setting.parentField}
            </Form.Item>
          </Col>
          <Col span={10}>
            <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
              <Select
                showSearch
                filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                onChange={this.selectScript}
              >
                {systemScripts.map((option, i) =>
                  <Select.Option style={{whiteSpace: 'normal'}} key={i} value={option.value}>{option.name}</Select.Option>
                )}
              </Select>
            </Form.Item>
          </Col>
          <Col span={6} className="add">
            <Button onClick={this.handleConfirm} loading={this.state.loading} className="mk-green" style={{marginTop: 5, marginBottom: 15, marginLeft: 30}}>
              保存
            </Button>
            <Button onClick={this.handleCancel} style={{marginTop: 5, marginBottom: 15, marginLeft: 10}}>
              取消
            </Button>
          </Col>
          <Col span={24} className="sql">
            <Form.Item label={'sql'}>
              {getFieldDecorator('sql', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + 'sql!'
                  }
                ]
              })(<TextArea rows={15} />)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(CustomForm)
src/templates/sharecomponent/treesettingcomponent/settingform/customscript/index.scss
New file
@@ -0,0 +1,31 @@
.modal-tree-menu-setting-script {
  .sqlfield {
    .ant-form-item {
      margin-bottom: 5px;
    }
    .ant-form-item-control {
      line-height: 24px;
    }
    .ant-form-item-label {
      line-height: 25px;
    }
    .ant-form-item-children {
      line-height: 22px;
    }
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
    }
  }
  .sql {
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
      padding-top: 4px;
    }
  }
}
src/templates/sharecomponent/treesettingcomponent/settingform/index.jsx
New file
@@ -0,0 +1,765 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Input, Radio, Select, Tooltip, Icon, notification, InputNumber, Modal, Table, Popconfirm, Typography, Button } from 'antd'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import SettingUtils from './utils.jsx'
import CustomScript from './customscript'
import './index.scss'
const { TextArea } = Input
const { confirm } = Modal
const { Paragraph } = Typography
class SettingForm extends Component {
  static propTpyes = {
    type: PropTypes.string,      // 菜单类型
    dict: PropTypes.object,      // 字典项
    menu: PropTypes.object,      // 菜单信息
    config: PropTypes.object,    // 页面配置信息
    formlist: PropTypes.array,   // 表单信息
    inputSubmit: PropTypes.any   // 回车提交事件
  }
  state = {
    formlist: [],
    btnloading: false,
    setting: null,
    view: 'normal',
    systemScripts: [{
      name: '默认sql',
      value: ''
    }],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '70%',
        render: (text) => (
          <Paragraph copyable ellipsis={{ rows: 5, expandable: true }}>{text}</Paragraph>
        )
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '10%',
        render: (text, record) => record.status === 'false' ?
          (
            <div>
              {this.props.dict['header.form.status.forbidden']}
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              {this.props.dict['header.form.status.open']}
              <Icon style={{marginLeft: '5px'}} type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '20%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record)} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.up']} onClick={() => this.handleUpDown(record, 'up')} style={{color: '#1890ff'}}><Icon type="arrow-up" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.down']} onClick={() => this.handleUpDown(record, 'down')} style={{color: '#ff4d4f'}}><Icon type="arrow-down" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record)} style={{color: '#8E44AD'}}><Icon type="swap" /></span>
            <Popconfirm
              title={this.props.dict['header.form.query.delete']}
              okText={this.props.dict['model.confirm']}
              cancelText={this.props.dict['header.cancel']}
              onConfirm={() => this.handleDelete(record)
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  UNSAFE_componentWillMount() {
    let _formlist = fromJS(this.props.formlist).toJS()
    let interType = 'inner'
    _formlist.forEach(item => {
      if (item.key === 'interType') {
        interType = item.initVal
      }
    })
    let _setting = fromJS(this.props.config.setting).toJS()
    _setting.scripts = _setting.scripts || []
    _setting.default = _setting.default || 'true'
    this.setState({
      setting: _setting,
      formlist: _formlist.map(item => {
        if (interType === 'inner' && ['sysInterface', 'interface', 'outerFunc'].includes(item.key)) {
          item.hidden = true
        } else if (interType === 'outer' && ['innerFunc', 'dataresource', 'queryType'].includes(item.key)) {
          item.hidden = true
        }
        return item
      })
    })
  }
  componentDidMount () {
    this.getsysScript()
  }
  getsysScript = () => {
    let _scriptSql = `Select distinct func+Remark as funcname,longparam, s.Sort from  s_custom_script s inner join (select OpenID from sapp where ID=@Appkey@) p on s.openid = case when s.appkey='' then s.openid else p.OpenID end order by s.Sort`
    _scriptSql = Utils.formatOptions(_scriptSql)
    let _sParam = {
      func: 'sPC_Get_SelectedList',
      LText: _scriptSql,
      obj_name: 'data',
      arr_field: 'funcname,longparam'
    }
    _sParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
    _sParam.secretkey = Utils.encrypt(_sParam.LText, _sParam.timestamp)
    Api.getSystemConfig(_sParam).then(res => {
      if (res.status) {
        let _scripts = res.data.map(item => {
          let _item = {
            name: item.funcname,
            value: Utils.formatOptions(item.longparam, true)
          }
          return _item
        })
        this.setState({
          systemScripts: [...this.state.systemScripts, ..._scripts]
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  handleConfirm = (otype) => {
    const { menu } = this.props
    const { view, setting } = this.state
    // 表单提交时检查输入值是否正确
    if (view !== 'custom') {
      return new Promise((resolve, reject) => {
        this.props.form.validateFieldsAndScroll((err, values) => {
          if (!err) {
            values = {...setting, ...values}
            // 数据源前端验证
            if (values.interType === 'inner' && !values.innerFunc && values.default !== 'false' && !values.dataresource) {
              notification.warning({
                top: 92,
                message: '请填写内部函数或数据源!',
                duration: 5
              })
              reject()
              return
            } else if (values.interType === 'inner' && !values.innerFunc && values.default !== 'false' && values.dataresource) {
              let error = Utils.verifySql(values.dataresource)
              if (error) {
                notification.warning({
                  top: 92,
                  message: '数据源中不可使用' + error,
                  duration: 5
                })
                reject()
                return
              }
            }
            // 数据源保存
            if (
              values.interType === 'inner' && !values.innerFunc &&
              values.default !== 'false' &&
              /[^\s]+\s+[^\s]+/ig.test(values.dataresource) &&
              this.props.config.setting.dataresource !== values.dataresource
            ) {
              let param = {
                func: 's_DataSrc_Save',
                LText: values.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 (otype === 'change') {
              this.setState({
                setting: values,
              }, () => {
                resolve()
              })
            } else {
              values.customScript = this.getCustomScript(values)
              this.sqlverify(values, resolve, reject)
            }
          } else {
            reject(err)
          }
        })
      })
    } else {
      let _setting = fromJS(this.state.setting).toJS()
      _setting.customScript = this.getCustomScript(_setting)
      let _this = this
      return new Promise((resolve, reject) => {
        if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
          confirm({
            content: `存在未保存项,确定提交吗?`,
            okText: this.props.dict['model.confirm'],
            cancelText: this.props.dict['header.cancel'],
            onOk() {
              _this.sqlverify(_setting, resolve, reject)
            },
            onCancel() {
              reject()
            }
          })
        } else {
          this.sqlverify(_setting, resolve, reject)
        }
      })
    }
  }
  getCustomScript = (setting) => {
    let _customScript = ''
    if (setting.scripts && setting.scripts.length > 0) {
      setting.scripts.forEach(item => {
        if (item.status === 'false') return
        _customScript += `
          ${item.sql}
        `
      })
    }
    if (_customScript) {
      _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
    return _customScript
  }
  sqlverify = (_setting, _resolve, _reject, isChange = false) => {
    if (!isChange && _setting.interType === 'inner' && !_setting.innerFunc && _setting.default === 'false' && !_setting.customScript) {
      notification.warning({
        top: 92,
        message: '不执行默认sql时,请添加自定义脚本!',
        duration: 5
      })
      _reject()
      return
    }
    if (_setting.interType === 'inner' && !_setting.innerFunc && _setting.default !== 'false') {
      let param = {
        func: 's_debug_sql',
        LText: SettingUtils.getDebugSql(_setting)
      }
      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) {
          _resolve(_setting)
        } else {
          _reject()
          Modal.error({
            title: result.message
          })
        }
      })
    } else {
      _resolve(_setting)
    }
  }
  selectChange = (key, val) => {
    if (key === 'primaryKey' && val) {
      this.props.form.setFieldsValue({
        order: `${val} desc`
      })
    }
  }
  onRadioChange = (e, key) => {
    let value = e.target.value
    let _formlist = fromJS(this.state.formlist).toJS()
    if (key === 'interType') {
      this.setState({
        formlist: _formlist.map(item => {
          item.hidden = false
          if (value === 'inner' && ['sysInterface', 'interface', 'outerFunc'].includes(item.key)) {
            item.initVal = this.props.form.getFieldValue(item.key)
            item.hidden = true
          } else if (value === 'outer' && ['innerFunc', 'dataresource', 'queryType'].includes(item.key)) {
            item.initVal = this.props.form.getFieldValue(item.key)
            item.hidden = true
          }
          return item
        })
      })
    } else if (key === 'sysInterface') {
      if (value === 'true') {
        this.props.form.setFieldsValue({
          interface: window.GLOB.mainSystemApi || ''
        })
      }
      this.setState({
        formlist: _formlist.map(item => {
          if (item.key === 'interface') {
            item.readonly = value === 'true'
          }
          return item
        })
      })
    }
  }
  changeView = () => {
    const { view } = this.state
    let _this = this
    if (view === 'normal') {
      this.handleConfirm('change').then(() => {
        const { setting } = this.state
        if (setting.interType !== 'inner' || (setting.interType === 'inner' && setting.innerFunc)) {
          notification.warning({
            top: 92,
            message: '使用外部接口或内部接口的自定义函数,不可添加自定义设置!',
            duration: 5
          })
          return
        }
        let _dataresource = setting.dataresource
        if (/\s/.test(_dataresource)) {
          _dataresource = '(' + _dataresource + ') tb'
        }
        let arr_field = `${setting.valueField}, ${setting.labelField}, ${setting.parentField}`
        let LText = `select ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by @orderBy@) as rows from ${_dataresource}) tmptable order by tmptable.rows`
        let _scripts = fromJS(this.state.systemScripts).toJS()
        _scripts[0].value = LText
        if (setting.default === 'false') {
          this.setState({
            view: 'custom',
            btnloading: false,
            systemScripts: _scripts
          })
          this.scrolltop()
        } else {
          this.setState({
            btnloading: true
          })
          new Promise((resolve, reject) => {
            this.sqlverify(setting, resolve, reject, true)
          }).then(() => {
            this.setState({
              view: 'custom',
              btnloading: false,
              systemScripts: _scripts
            })
            this.scrolltop()
          }, () => {
            this.setState({
              btnloading: false
            })
          })
        }
      })
    } else {
      let _loading = false
      if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
        _loading = true
      }
      if (_loading) {
        confirm({
          content: `存在未保存项,确定切换吗?`,
          okText: this.props.dict['model.confirm'],
          cancelText: this.props.dict['header.cancel'],
          onOk() {
            _this.setState({
              view: 'normal'
            })
            _this.scrolltop()
          },
          onCancel() {}
        })
      } else {
        _this.setState({
          view: 'normal'
        })
        _this.scrolltop()
      }
    }
  }
  handleSubmit = (e) => {
    e.preventDefault()
    if (this.props.inputSubmit) {
      this.props.inputSubmit()
    }
  }
  getFields(formlist) {
    const { getFieldDecorator } = this.props.form
    const fields = []
    formlist.forEach((item, index) => {
      if (item.hidden || item.forbid) return
      if (item.type === 'text') { // 文本搜索
        let rules = item.rules || []
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement={item.placement || 'topLeft'} title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  },
                  ...rules
                ]
              })(<Input placeholder={item.placeholder || ''} autoComplete="off" disabled={item.readonly} onPressEnter={this.handleSubmit} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'number') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || 6,
                rules: [
                  {
                    required: item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<InputNumber min={item.min} max={item.max} precision={0} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || '',
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => {
                    return option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0 ||
                      option.props.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
                  }}
                  onChange={(value) => {this.selectChange(item.key, value)}}
                  getPopupContainer={() => document.getElementById('model-table-setting-form')}
                >
                  {item.options.map((option, i) =>
                    <Select.Option id={i} key={i} value={option.value}>
                      {option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'radio') {
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.tooltip ?
              <Tooltip placement="topLeft" title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip> : item.label
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e, item.key)}}>
                  {
                    item.options.map((option, i) => {
                      return (
                        <Radio key={i} value={option.value}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>,
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'datasource') {
        fields.push(
          <Col span={24} key={index} style={{paddingLeft: '7px'}}>
            <Form.Item labelCol={{xs: { span: 24 }, sm: { span: 4 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 20 }} } help={item.help} label={
              <Tooltip placement="topLeft" title={item.tooltip}>
                <Icon type="question-circle" />
                {item.label}
              </Tooltip>
            }>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal
              })(<TextArea rows={4} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'textarea') {
        fields.push(
          <Col span={20} offset={4} key={index}>
            <Form.Item className="text-area">
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.input'] + item.label + '!'
                  }
                ]
              })(<TextArea rows={4} />)}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'multiselect') { // 多选
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal || []
              })(
                <Select
                  showSearch
                  mode="multiple"
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                >
                  {item.options.map((option, i) =>
                    <Select.Option id={i} key={i} value={option.value}>{option.text}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleEdit = (record) => {
    this.scriptsForm.edit(record)
    this.scrolltop()
  }
  scrolltop = () => {
    let node = document.getElementById('model-tree-setting-form-box').parentNode
    if (node && node.scrollTop) {
      let inter = Math.ceil(node.scrollTop / 10)
      let timer = setInterval(() => {
        if (node.scrollTop - inter > 0) {
          node.scrollTop = node.scrollTop - inter
        } else {
          node.scrollTop = 0
          clearInterval(timer)
        }
      }, 10)
    }
  }
  handleUpDown = (record, direction) => {
    let scripts = fromJS(this.state.setting.scripts).toJS()
    let index = 0
    scripts = scripts.filter((item, i) => {
      if (item.uuid === record.uuid) {
        index = i
      }
      return item.uuid !== record.uuid
    })
    if ((index === 0 && direction === 'up') || (index === scripts.length && direction === 'down')) {
      return
    }
    if (direction === 'up') {
      scripts.splice(index - 1, 0, record)
    } else {
      scripts.splice(index + 1, 0, record)
    }
    this.setState({
      setting: {...this.state.setting, scripts: scripts}
    })
  }
  handleStatus = (record) => {
    let scripts = fromJS(this.state.setting.scripts).toJS()
    record.status = record.status === 'false' ? 'true' : 'false'
    scripts = scripts.map(item => {
      if (item.uuid === record.uuid) {
        return record
      } else {
        return item
      }
    })
    this.setState({
      setting: {...this.state.setting, scripts: scripts}
    })
  }
  handleDelete = (record) => {
    let scripts = fromJS(this.state.setting.scripts).toJS()
    scripts = scripts.filter(item => item.uuid !== record.uuid)
    this.setState({ setting: {...this.state.setting, scripts: scripts} })
  }
  scriptsChange = (values) => {
    let scripts = fromJS(this.state.setting.scripts).toJS()
    if (values.uuid) {
      scripts = scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      values.uuid = Utils.getuuid()
      scripts.push(values)
    }
    this.setState({
      setting: {...this.state.setting, scripts: scripts}
    })
  }
  render() {
    const { config, type } = this.props
    const { formlist, view, setting, scriptsColumns, systemScripts, btnloading } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-tree-setting-form-box" id="model-tree-setting-form-box">
        {view ==='custom' ? <div>
          <Icon className="setting-custom-back" onClick={this.changeView} type="arrow-left" />
          <CustomScript
            type={type}
            setting={setting}
            dict={this.props.dict}
            searches={config.search}
            systemScripts={systemScripts}
            scriptsChange={this.scriptsChange}
            wrappedComponentRef={(inst) => this.scriptsForm = inst}
          />
          <Table
            bordered
            rowKey="uuid"
            className="custom-table"
            dataSource={setting.scripts}
            columns={scriptsColumns}
            pagination={false}
          />
        </div> : null }
        <Form {...formItemLayout} className="model-tree-setting-form" id="model-table-setting-form">
          {view !=='custom' ? <Row gutter={24}>{this.getFields(formlist)}</Row> : null}
          <Row gutter={24}>
            {view !=='custom' ? <Button onClick={this.changeView} className="to-custom-script" loading={btnloading}>自定义设置<Icon style={{marginLeft: 5}} type="right" /></Button> : null}
            {view ==='custom' ? <span onClick={this.changeView} style={{float: 'left', color: '#1890ff', marginLeft: 12, marginTop: 15, cursor: 'pointer'}}><Icon style={{marginRight: 5}} type="left" />基础设置</span> : null}
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/templates/sharecomponent/treesettingcomponent/settingform/index.scss
New file
@@ -0,0 +1,45 @@
.model-tree-setting-form-box {
  position: relative;
  .model-tree-setting-form {
    .textarea {
      .ant-form-item-label {
        width: 16.3%;
      }
      .ant-form-item-control-wrapper {
        width: 83.33333333%;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
  }
  .operation-btn {
    display: inline-block;
    font-size: 16px;
    padding: 0 5px;
    cursor: pointer;
  }
  td {
    word-break: break-all;
  }
  .setting-custom-back {
    position: absolute;
    top: -20px;
    left: -10px;
    font-size: 16px;
    z-index: 1;
    cursor: pointer;
    padding: 10px;
    color: rgb(24, 144, 255);
  }
  .to-custom-script {
    float: right;
    color: #1890ff;
    margin-right: 12px;
    margin-top: 15px;
    cursor: pointer;
    border: 0;
    box-shadow: unset;
  }
}
src/templates/sharecomponent/treesettingcomponent/settingform/utils.jsx
New file
@@ -0,0 +1,51 @@
export default class SettingUtils {
  /**
   * @description 生成页面查询语句
   * @return {Object}  setting       页面设置
   */
  static getDebugSql (setting) {
    let sql = ''
    let _dataresource = setting.dataresource
    let _customScript = setting.customScript
    if (setting.interType === 'inner' && !setting.innerFunc && setting.default === 'false') {
      _dataresource = ''
    }
    if (_dataresource) {
      _dataresource = _dataresource.replace(/@\$|\$@/ig, '')
    }
    if (_customScript) {
      _customScript = _customScript.replace(/@\$|\$@/ig, '')
    }
    if (_customScript) {
      _customScript = _customScript.replace(/@orderBy@/ig, setting.order)
    }
    // 数据源处理
    if (_dataresource) {
      if (/\s/.test(_dataresource)) {
        _dataresource = '(' + _dataresource + ') tb'
      }
      let arr_field = `${setting.valueField}, ${setting.labelField}, ${setting.parentField}`
      _dataresource = `select ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${setting.order}) as rows from ${_dataresource}) tmptable order by tmptable.rows`
    }
    if (_customScript) {
      sql = `${_customScript}
        ${_dataresource}
        aaa:
        if @ErrorCode!=''
          insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
      `
    } else {
      sql = _dataresource
    }
    return sql
  }
}
src/templates/subtableconfig/index.jsx
@@ -33,7 +33,7 @@
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 CardComponent = asyncComponent(() => import('@/templates/sharecomponent/cardcomponent'))
class SubTableConfig extends Component {
  static propTpyes = {
@@ -1169,6 +1169,16 @@
                        />
                      </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}>
src/templates/treepageconfig/index.jsx
New file
@@ -0,0 +1,921 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { is, fromJS } from 'immutable'
import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import { Button, Card, Modal, Collapse, notification, Spin, Icon, Switch, Tooltip, Row, Col, Tree } 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 asyncComponent from '@/utils/asyncComponent'
import MenuForm from '@/templates/zshare/menuform'
import EditComponent from '@/templates/zshare/editcomponent'
import SourceElement from '@/templates/zshare/dragsource'
import Source from './source'
import './index.scss'
const { Panel } = Collapse
const { confirm } = Modal
const { TreeNode } = Tree
const TreeSettingComponent = asyncComponent(() => import('@/templates/sharecomponent/treesettingcomponent'))
const TabsComponent = asyncComponent(() => import('@/templates/sharecomponent/tabscomponent'))
class ComTableConfig extends Component {
  static propTpyes = {
    menu: PropTypes.any,
    optionLibs: PropTypes.any,
    reloadmenu: PropTypes.func,
    handleView: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    config: null,            // 页面配置
    menuformlist: null,      // 基本信息表单字段
    formlist: null,          // 搜索条件、按钮、显示列表单字段
    menuloading: false,      // 菜单保存中
    menucloseloading: false, // 菜单关闭时,选择保存
    loading: false,          // 加载中,页面spin
    closeVisible: false,     // 关闭模态框
    tables: [],              // 可用表名
    originMenu: null,        // 原始菜单
    delTabs: [],             // 删除标签列表
    tabviews: [],            // 所有标签页
    optionLibs: null,        // 自定义下拉选项库
    activeKey: '0',          // 默认展开基本信息
    pasteContent: null,      // 粘贴配置信息
    openEdition: ''          // 编辑版本标记,防止多人操作
  }
  /**
   * @description 数据预处理
   * 1、设置页面配置信息,新建或无配置信息时(切换模板后无配置信息),使用模板默认配置
   * 2、设置操作类型、原始菜单信息(每次保存后重置)、已使用表及基本信息表单
   */
  UNSAFE_componentWillMount () {
    const { menu, optionLibs } = this.props
    let _LongParam = menu.LongParam
    let _config = ''
    if (!_LongParam) {
      _config = fromJS(Source.baseConfig).toJS()
      _config.isAdd = true
    } else {
      _config = _LongParam
    }
    // 配置默认值,兼容
    _config.Template = 'TreePage'
    _config.easyCode = _config.easyCode || ''
    if (_config.type === 'user') {
      // 重置标签ID
      _config.tabgroups.forEach(group => {
        group.sublist = group.sublist.map(tab => {
          tab.uuid = Utils.getuuid()
          if (tab.linkTab) {
            tab.linkTab = ''
          }
          return tab
        })
      })
    }
    this.setState({
      config: _config,
      openEdition: menu.open_edition || '',
      activeKey: menu.activeKey || '0',
      optionLibs: optionLibs,
      originMenu: fromJS(menu).toJS(),
      menuformlist: getMainMenuForm(menu, _config)
    })
  }
  /**
   * @description 加载完成后, 获取所有标签页信息
   */
  componentDidMount () {
    this.reloadTab(false)
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  /**
   * @description 加载或刷新标签信息
   */
  reloadTab = (type) => {
    this.setState({
      loading: type,
      tabviews: []
    })
    Api.getSystemConfig({func: 'sPC_Get_UserTemp', TypeCharTwo: 'tab'}).then(res => {
      if (res.status) {
        this.setState({
          loading: false,
          tabviews: res.UserTemp.map(temp => {
            return {
              uuid: temp.MenuID,
              value: temp.MenuID,
              text: temp.MenuName,
              type: temp.Template,
              MenuNo: temp.MenuNo
            }
          })
        })
        if (type) {
          notification.success({
            top: 92,
            message: '刷新成功。',
            duration: 2
          })
        }
      } else {
        this.setState({
          loading: false
        })
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  getFuncNames = (data, funcNames, tableNames) => {
    data.forEach(item => {
      if (item.subfuncs) {
        this.getFuncNames(item.subfuncs, funcNames, tableNames)
      } else {
        if (item.tableName) {
          tableNames.push(item.tableName)
        }
        if (item.innerFunc) {
          funcNames.push({func: item.innerFunc, label: item.label || ''})
        }
        if (item.callbackFunc) {
          funcNames.push({func: item.callbackFunc, label: item.label || ''})
        }
      }
    })
    return {
      func: funcNames,
      table: tableNames
    }
  }
  /**
   * @description 三级菜单保存
   */
  submitConfig = () => {
    const { menu } = this.props
    const { originMenu, delTabs, openEdition } = this.state
    let config = fromJS(this.state.config).toJS()
    this.menuformRef.handleConfirm().then(res => {
      if (config.isAdd) {
        config.tabgroups[0].sublist = config.tabgroups[0].sublist.filter(item => !item.origin)
      }
      if (config.type === 'user') { // 使用已有菜单时,默认添加关联标签id
        config.tabgroups.forEach(group => {
          group.sublist = group.sublist.map(tab => {
            if (!tab.linkTab) {
              tab.linkTab = Utils.getuuid()
            }
            return tab
          })
        })
      }
      let _LongParam = ''
      let _config = {...config, easyCode: res.easyCode}
      let _pageParam = {...menu.PageParam, OpenType: res.opentype}
      // 未设置数据源或标签不合法时,启用状态为false
      let vresult = this.verifyconfig(_config)
      if (vresult !== true) {
        _config.enabled = false
      }
      _config.funcs = [] // 页面及子页面存储过程集
      _config.funcs.push({
        type: 'view',
        subtype: 'view',
        uuid: menu.MenuID,
        intertype: _config.setting.interType || 'inner',
        interface: _config.setting.interface || '',
        tableName: _config.setting.tableName || '',
        innerFunc: _config.setting.innerFunc || '',
        outerFunc: _config.setting.outerFunc || ''
      })
      _config.tabgroups.forEach(group => {
        group.sublist.forEach(tab => {
          _config.funcs.push({
            type: 'tab',
            subtype: 'tab',
            uuid: tab.uuid,
            label: tab.label,
            linkTab: tab.linkTab
          })
        })
      })
      if (this.state.closeVisible) { // 显示关闭对话框时,模态框中保存按钮,显示保存中状态
        this.setState({
          menucloseloading: true
        })
      } else {
        this.setState({
          menuloading: true
        })
      }
      new Promise(resolve => {
        let deffers = []
        _config.funcs.forEach(item => {
          if (item.type === 'tab') {
            let deffer = new Promise(resolve => {
              Api.getSystemConfig({
                func: 'sPC_Get_LongParam',
                MenuID: item.linkTab
              }).then(result => {
                if (result.status && result.LongParam) {
                  let _LongParam = ''
                  if (result.LongParam) {
                    try {
                      _LongParam = JSON.parse(window.decodeURIComponent(window.atob(result.LongParam)))
                    } catch (e) {
                      console.warn('Parse Failure')
                      _LongParam = ''
                    }
                  }
                  if (_LongParam) {
                    item.menuNo = _LongParam.tabNo || ''
                    item.subfuncs = _LongParam.funcs || []
                  }
                }
                resolve()
              })
            })
            deffers.push(deffer)
          }
        })
        if (deffers.length === 0) {
          resolve()
        } else {
          Promise.all(deffers).then(() => {
            resolve()
          })
        }
      }).then(() => {
        // 保存时删除配置类型,system 、user
        delete _config.type
        delete _config.isAdd
        try {
          _LongParam = window.btoa(window.encodeURIComponent(JSON.stringify(_config)))
        } catch (e) {
          notification.warning({
            top: 92,
            message: '编译错误',
            duration: 5
          })
          this.setState({
            menucloseloading: false,
            menuloading: false
          })
          return
        }
        let _sort = 0
        let btntabs = []
        let tabParam = { // 添加菜单tab页
          func: 'sPC_sMenusTab_AddUpt',
          MenuID: menu.MenuID
        }
        let _LText = []
        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(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`)
          })
        })
        _LText = _LText.join(' union all ')
        tabParam.LText = Utils.formatOptions(_LText)
        tabParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        tabParam.secretkey = Utils.encrypt(tabParam.LText, tabParam.timestamp)
        let _vals = this.getFuncNames(_config.funcs, [], [])
        let _tables = Array.from(new Set(_vals.table))
        let param = {
          func: 'sPC_TrdMenu_AddUpt',
          FstID: res.fstMenuId,
          SndID: res.parentId,
          ParentID: res.parentId,
          MenuID: menu.MenuID,
          MenuNo: res.MenuNo,
          EasyCode: res.easyCode,
          Template: menu.PageParam.Template || '',
          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`),
          LTexttb: _tables.map(item => `select '${menu.MenuID}' as MenuID,'${item}' as tbName`)
        }
        if (menu.menuSort) { // 菜单新建时设置排序
          param.Sort = menu.menuSort
        }
        param.LText = param.LText.join(' union all ')
        param.LText = Utils.formatOptions(param.LText)
        param.LTexttb = param.LTexttb.join(' union all ')
        param.LTexttb = Utils.formatOptions(param.LTexttb)
        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 => {
          if (delTabs.length > 0) {
            let deffers = delTabs.map(item => {
              let _param = {
                func: 'sPC_MainMenu_Del',
                MenuID: item.uuid
              }
              return new Promise(resolve => {
                Api.getSystemConfig(_param).then(response => {
                  resolve(response)
                })
              })
            })
            Promise.all(deffers).then(result => {
              let error = null
              result.forEach(response => {
                if (!response.status) {
                  error = response
                }
              })
              if (error) {
                this.setState({
                  menuloading: false,
                  menucloseloading: false
                })
                notification.warning({
                  top: 92,
                  message: error.message,
                  duration: 5
                })
                resolve(false)
              } else {
                this.setState({
                  delTabs: []
                })
                resolve(true)
              }
            })
          } else if (delTabs.length === 0) {
            resolve(true)
          }
        }).then(resp => {
          if (resp === false) return
          let localParam = fromJS(param).toJS()
          Api.getSystemConfig(param).then(response => {
            if (response.status) {
              let _FMenu = originMenu.fstMenuList.filter(fstM => fstM.MenuID === res.fstMenuId)[0]
              let _supMenuList = []
              if (_FMenu) {
                _supMenuList = _FMenu.options
              }
              this.setState({
                config: _config,
                openEdition: response.open_edition || '',
                originMenu: {
                  ...originMenu,
                  LongParam: _config,
                  PageParam: _pageParam,
                  MenuName: res.MenuName,
                  MenuNo: res.MenuNo,
                  ParentID: res.parentId,
                  fstMenuId: res.fstMenuId,
                  supMenuList: _supMenuList
                }
              })
              this.props.reloadmenu()
              // 存在标签页时
              if (tabParam.LText) {
                Api.getSystemConfig(tabParam).then(result => {
                  if (result.status) {
                    notification.success({
                      top: 92,
                      message: '保存成功',
                      duration: 2
                    })
                    if (this.state.closeVisible) {
                      this.props.handleView()
                    } else {
                      this.setState({
                        menuloading: false,
                        menucloseloading: false
                      })
                    }
                  } else {
                    notification.warning({
                      top: 92,
                      message: result.message,
                      duration: 5
                    })
                    this.setState({
                      menuloading: false,
                      menucloseloading: false
                    })
                  }
                })
              } else {
                notification.success({
                  top: 92,
                  message: '保存成功',
                  duration: 2
                })
                if (this.state.closeVisible) {
                  this.props.handleView()
                } else {
                  this.setState({
                    menuloading: false,
                    menucloseloading: false
                  })
                }
              }
              localParam.func = 'sPC_TrdMenu_AddUpt_For_Local'
              delete localParam.LongParam
              delete localParam.PageParam
              delete localParam.Template
              delete localParam.Sort
              delete localParam.EasyCode
              delete localParam.open_edition
              Api.getLocalConfig(localParam)
            } else {
              this.setState({
                menuloading: false,
                menucloseloading: false
              })
              notification.warning({
                top: 92,
                message: response.message,
                duration: 5
              })
            }
          })
        })
      })
    }, () => {
      notification.warning({
        top: 92,
        message: this.state.dict['header.menu.basemsg'],
        duration: 5
      })
    })
  }
  /**
   * @description 点击返回时,判断配置保存状态
   */
  cancelConfig = () => {
    const { menu } = this.props
    const { config, originMenu } = this.state
    let _this = this
    if (config.isAdd) {
      confirm({
        content: '菜单尚未提交,确定放弃保存吗?',
        okText: this.state.dict['model.confirm'],
        cancelText: this.state.dict['header.cancel'],
        onOk() {
          _this.props.handleView()
        },
        onCancel() {}
      })
    } else {
      this.menuformRef.handleConfirm().then(res => {
        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,
          ParentID: res.parentId,
          fstMenuId: res.fstMenuId
        }
        if (!is(fromJS(originMenu), fromJS(_originMenu))) {
          this.setState({
            closeVisible: true
          })
        } else {
          this.props.handleView()
        }
      }, () => {
        this.setState({
          closeVisible: true
        })
      })
    }
  }
  /**
   * @description 设置可配置按钮
   */
  setSubConfig = (item, type) => {
    const { menu } = this.props
    const { config, originMenu, optionLibs, activeKey, openEdition } = this.state
    if (!originMenu.MenuID) { // menuID不存在时,为新建菜单,提示菜单尚未保存
      notification.warning({
        top: 92,
        message: this.state.dict['header.menu.config.notsave'],
        duration: 5
      })
    } else {
      this.menuformRef.handleConfirm().then(res => {
        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,
          ParentID: res.parentId,
          fstMenuId: res.fstMenuId
        }
        if (!is(fromJS(originMenu), fromJS(_originMenu))) { // 菜单信息变化时,提示保存
          notification.warning({
            top: 92,
            message: this.state.dict['header.menu.config.update'],
            duration: 5
          })
          return
        }
        _originMenu.activeKey = activeKey       // 保存当前打开页签
        _originMenu.open_edition = openEdition  // 更新版本号
        let param = {
          optionLibs: optionLibs,
          editMenu: _originMenu,
          editTab: item,
          tabConfig: null,
          editSubTab: null,
          subTabConfig: null,
          btnTab: null,
          btnTabConfig: null,
          editAction: null,
          subConfig: '',
          tabview: item.type
        }
        this.setState({
          loading: true
        })
        Api.getSystemConfig({
          func: 'sPC_Get_LongParam',
          MenuID: item.linkTab
        }).then(res => {
          if (res.status) {
            this.setState({
              loading: false
            })
            let _LongParam = ''
            if (res.LongParam) {
              try {
                _LongParam = JSON.parse(window.decodeURIComponent(window.atob(res.LongParam)))
              } catch (e) {
                console.warn('Parse Failure')
                _LongParam = ''
              }
            }
            if (_LongParam && param.tabview === 'SubTable' && _LongParam.Template === 'SubTable') {
              param.subConfig = _LongParam
            }
            if (param.editTab) {
              param.editTab.open_edition = res.open_edition || ''
            }
            this.props.handleView(param)
          } else {
            this.setState({
              loading: false
            })
            notification.warning({
              top: 92,
              message: res.message,
              duration: 5
            })
          }
        })
      }, () => {
        notification.warning({
          top: 92,
          message: this.state.dict['header.menu.config.update'],
          duration: 5
        })
      })
    }
  }
  /**
   * @description 页面启用停止切换
   */
  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 (group.sublist.length === 0) {
          tabinvalid = false
        }
      })
    }
    if (!config.setting.tableName || !config.setting.valueField || !config.setting.labelField || !config.setting.parentField) {
      return '请完善树形结构配置信息!'
    } else if (!tabinvalid) {
      return '菜单标签页设置错误(存在多行标签时,行标签不可为空)!'
    } else {
      return true
    }
  }
  /**
   * @description 选择不保存时,如有复制按钮,则删除
   */
  notsave = () => {
    this.props.handleView()
  }
  /**
   * @description 编辑功能完成更新,包括解冻按钮、粘贴、替换等
   */
  editConfig = (res) => {
    if (res.type === 'paste') {
      this.setState({
        pasteContent: res.content
      }, () => {
        this.setState({
          pasteContent: null
        })
      })
    }
  }
  /**
   * @description 更新标签配置信息
   */
  updatetabs = (config, delcards) => {
    const { delTabs } = this.state
    this.setState({
      config: config,
      delTabs: delcards ? [...delTabs, ...delcards] : delTabs
    })
  }
  /**
   * @description 更新配置信息
   */
  updateconfig = (config) => {
    this.setState({
      config: config
    })
  }
  render () {
    const { activeKey, config } = this.state
    let configTabs = []
    config.tabgroups.forEach(group => {
      configTabs.push(...group.sublist)
    })
    return (
      <div className="tree-page-board">
        <DndProvider backend={HTML5Backend}>
          {/* 工具栏 */}
          <div className="tools">
            <Collapse accordion defaultActiveKey={activeKey} bordered={false} onChange={(key) => this.setState({activeKey: key})}>
              {/* 基本信息 */}
              <Panel forceRender={true} header={this.state.dict['header.menu.basedata']} key="0" id="main-basedata">
                {/* 菜单信息 */}
                <MenuForm
                  dict={this.state.dict}
                  formlist={this.state.menuformlist}
                  wrappedComponentRef={(inst) => this.menuformRef = inst}
                />
              </Panel>
              {/* 添加标签 */}
              <Panel header={this.state.dict['header.menu.tab']} key="4">
                <div className="search-element">
                  {Source.tabItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                {configTabs.length > 0 ?
                  <p className="config-btn-title">
                    <Tooltip placement="topLeft" title="点击按钮,可完成或查看标签配置信息。">
                      <Icon type="question-circle" />
                    </Tooltip>
                    {this.state.dict['header.menu.tab.configurable']}
                  </p> : null
                }
                {configTabs.map((item, index) => {
                  return (
                    <div key={index}>
                      <Button
                        className="config-button"
                        icon={item.icon}
                        style={{marginBottom: '10px'}}
                        onClick={() => this.setSubConfig(item, 'tab')}
                      >{item.label}</Button>
                    </div>
                  )
                })}
              </Panel>
            </Collapse>
          </div>
          <div className="setting">
            <Card title={
              <div>
                {this.state.dict['header.menu.page.configurable']}
                <Icon type="redo" style={{marginLeft: '10px'}} title="刷新标签列表" onClick={() => this.reloadTab(true)} />
              </div>
            } bordered={false} extra={
              <div>
                <EditComponent dict={this.state.dict} type="TreePage" config={this.state.config} MenuID={this.props.menu.MenuID} refresh={this.editConfig}/>
                <Switch className="big" checkedChildren="启" unCheckedChildren="停" checked={this.state.config.enabled} onChange={this.onEnabledChange} />
                <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%' }}>
              <Row gutter={16}>
                <Col span={config.setting.width}>
                  <TreeSettingComponent
                    config={config}
                    MenuID={this.props.menu.MenuID}
                    menuformRef={this.menuformRef}
                    permFuncField={this.props.permFuncField}
                    updatesetting={this.updateconfig}
                  />
                  <Card
                    className="tree-card"
                    title={
                      <span className="tree-title">
                        <span className="title">{config.setting.title}</span>
                        {config.setting.searchable !== 'false' ? <span className="ant-input-search ant-input-affix-wrapper"><span className="ant-input-suffix"><Icon type="search" /></span></span> : null}
                      </span>
                    }
                    bordered={false}
                  >
                    <div className="tree-box">
                      <Tree
                        defaultExpandAll={true}
                        blockNode
                        showIcon={config.setting.showIcon === 'true'}
                        showLine={config.setting.showLine === 'true'}
                      >
                        <TreeNode icon={<Icon type="folder-open" />} title="parent 0" key="0-0">
                          <TreeNode icon={<Icon type="file" />} title="leaf 0-0" key="0-0-0" isLeaf />
                          <TreeNode icon={<Icon type="file" />} title="leaf 0-1" key="0-0-1" isLeaf />
                        </TreeNode>
                        <TreeNode icon={<Icon type="folder-open" />} title="parent 1" key="0-1">
                          <TreeNode icon={<Icon type="file" />} title="leaf 1-0" key="0-1-0" isLeaf />
                          <TreeNode icon={<Icon type="file" />} title="leaf 1-1" key="0-1-1" isLeaf />
                        </TreeNode>
                      </Tree>
                    </div>
                  </Card>
                </Col>
                <Col span={24 - config.setting.width}>
                  {/* 标签组 */}
                  <TabsComponent
                    config={config}
                    tabs={this.state.tabviews}
                    setSubConfig={(item) => this.setSubConfig(item, 'tab')}
                    updatetabs={this.updatetabs}
                  />
                </Col>
              </Row>
            </Card>
          </div>
        </DndProvider>
        {/* 返回时未保存提示 */}
        <Modal
          bodyStyle={{textAlign: 'center', color: '#000000', fontSize: '16px'}}
          closable={false}
          maskClosable={false}
          visible={this.state.closeVisible}
          onCancel={() => { this.setState({closeVisible: false}) }}
          footer={[
            <Button key="save" className="mk-btn mk-green" loading={this.state.menucloseloading} onClick={this.submitConfig}>{this.state.dict['header.save']}</Button>,
            <Button key="notsave" className="mk-btn mk-yellow" onClick={this.notsave}>{this.state.dict['header.notsave']}</Button>,
            <Button key="cancel" onClick={() => { this.setState({closeVisible: false}) }}>{this.state.dict['header.cancel']}</Button>
          ]}
          destroyOnClose
        >
          {this.state.dict['header.menu.config.placeholder']}
        </Modal>
        {this.state.loading && <Spin size="large" />}
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    sysRoles: state.sysRoles,
    permFuncField: state.permFuncField,
    memberLevel: state.memberLevel
  }
}
const mapDispatchToProps = () => {
  return {}
}
export default connect(mapStateToProps, mapDispatchToProps)(ComTableConfig)
src/templates/treepageconfig/index.scss
New file
@@ -0,0 +1,266 @@
.tree-page-board {
  position: fixed;
  z-index: 1070;
  padding-top: 48px;
  top: 0px;
  left: 0px;
  right: 0px;
  bottom: 0px;
  background: rgba(0, 0, 0, 0.35);
  display: flex;
  .tools {
    flex: 1;
    background: #ffffff;
    border-right: 1px solid #d9d9d9;
    height: 100%;
    overflow-y: hidden;
    padding-bottom: 30px;
    .ant-collapse-item {
      position: relative;
      border: 0;
    }
    .ant-input-search {
      margin-top: 10px;
    }
    .ant-collapse-item.ant-collapse-item-active {
      border-bottom: 1px solid #d9d9d9;
    }
    .ant-collapse .ant-collapse-header {
      padding: 11px 16px 10px 40px;
      border-bottom: 1px solid #d9d9d9;
      background: #1890ff;
      color: #ffffff;
    }
    .ant-collapse-content-box {
      .ant-form-item {
        margin-bottom: 10px;
        .ant-form-item-label {
          text-align: left;
          height: 25px;
          line-height: 25px;
        }
      }
    }
    .search-element {
      padding-top: 10px;
      li {
        padding: 0px 16px 10px;
        div {
          cursor: move;
        }
      }
    }
    .ant-list {
      margin-top: 20px;
      .ant-list-item {
        display: -webkit-box;
        padding-right: 20px;
        position: relative;
        padding-left: 5px;
        overflow: hidden;
        text-overflow: ellipsis;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        min-height: 55px;
        width: 100%;
        .anticon {
          position: absolute;
          top: 0px;
          right: 0px;
          padding: 3px 3px 10px 10px;
          cursor: pointer;
        }
        .bottom-mask {
          position: absolute;
          width: 100%;
          height: 8px;
          bottom: 0;
          left: 0;
          background: #ffffff;
          border-radius: 8px;
        }
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
    .config-button {
      min-width: 65px;
    }
  }
  .tools:hover {
    overflow-y: auto;
  }
  .tools::-webkit-scrollbar {
    width: 7px;
  }
  .tools::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0);
    background: rgba(0, 0, 0, 0);
  }
  .tools::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0);
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0);
    background: rgba(0, 0, 0, 0);
  }
  .tools:hover::-webkit-scrollbar-thumb {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
  }
  .tools:hover::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
    border: 1px solid rgba(0, 0, 0, 0.07);
  }
  .setting {
    position: relative;
    width: calc(100vw - 235px);
    height: 100%;
    background: #ffffff;
    .ant-switch.big {
      min-width: 60px;
      height: 28px;
      line-height: 28px;
      margin-top: -2px;
      .ant-switch-inner {
        font-size: 14px;
      }
    }
    .ant-switch.big::after {
      width: 24px;
      height: 24px;
    }
    .ant-card-head {
      min-height: 44px;
    }
    .ant-card-head-title {
      padding: 5px 0;
      color: #1890ff;
    }
    .ant-card-extra {
      padding: 5px 0;
      button {
        margin-left: 20px;
      }
    }
    .ant-card-body {
      position: relative;
      padding: 10px 10px 40px;
      .tree-card {
        min-height: calc(100vh - 125px);
        box-shadow: 0px 0px 2px #dddddd;
        .ant-card-head {
          padding: 0 10px;
          .ant-card-head-title {
            padding: 15px 0 10px;
            .tree-title {
              display: inline-block;
              width: 100%;
              color: #1890ff;
              .ant-input-affix-wrapper {
                width: calc(100% - 140px);
                max-width: 130px;
                margin-top: 0px;
                float: right;
                height: 28px;
                border-radius: 20px;
                border: 1px solid #d9d9d9;
                opacity: 0.6;
              }
              .title {
                display: inline-block;
                max-width: 50%;
                overflow: hidden;
                text-overflow:ellipsis;
                white-space: nowrap;
              }
            }
          }
        }
        >.ant-card-body {
          padding: 0px;
          .ant-menu-inline {
            border-right: 0;
            margin-top: 2px;
          }
          .tree-box {
            min-height: calc(100vh - 180px);
            overflow-x: auto;
          }
          .tree-box::-webkit-scrollbar {
            height: 10px;
          }
          .tree-box::-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);
          }
          .tree-box::-webkit-scrollbar-track {/*滚动条里面轨道*/
            box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
            border-radius: 3px;
            border: 1px solid rgba(0, 0, 0, 0.07);
            background: rgba(0, 0, 0, 0);
          }
          .ant-tree-node-selected
          .ant-tree.ant-tree-directory > li span.ant-tree-node-content-wrapper::before,
          .ant-tree.ant-tree-directory .ant-tree-child-tree > li span.ant-tree-node-content-wrapper::before {
            right: 0px;
          }
        }
      }
      .model-table-tab-list .tab-line-list {
        padding-top: 0px;
        > .anticon-question-circle {
          top: 10px;
        }
        > .anticon-plus {
          top: 15px;
        }
        > .ant-row .ant-tabs-tab .edit {
          top: 10px;
        }
        .anticon-arrow-down, .anticon-arrow-up, .anticon-delete {
          top: 15px;
        }
      }
      .model-tree-menu-setting > .anticon-setting {
        font-size: 16px;
        right: 0px;
        top: -9px;
      }
    }
  }
  .setting {
    overflow-y: scroll;
  }
  .setting::-webkit-scrollbar {
    width: 7px;
  }
  .setting::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.13);
    background: rgba(0, 0, 0, 0.13);
    display: none;
  }
  .setting::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.07);
    background: rgba(0, 0, 0, 0);
  }
  .setting:hover::-webkit-scrollbar-thumb {
    display: block;
  }
  .ant-spin {
    position: absolute;
    margin-left: calc(50vw - 22px);
    margin-top: 30vh;
  }
}
src/templates/treepageconfig/source.jsx
New file
@@ -0,0 +1,57 @@
import Utils from '@/utils/utils.js'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
const CommonDict = localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
class CommonTableBaseData {
  baseConfig = {
    type: 'system',
    Template: 'TreePage',
    enabled: false,
    setting: {
      tableName: '',
      title: 'Tree',
      order: '',
      dataresource: '',
      interType: 'inner',
      sysInterface: 'false',
      searchable: 'true',
      innerFunc: '',
      interface: '',
      outerFunc: '',
      width: 5
    },
    tabgroups: [{
      uuid: 'tabs',
      sublist: [
        {
          origin: true,          // 是否为示例
          uuid: Utils.getuuid(),
          label: 'tab1',
          type: 'SubTable',
          linkTab: '',
          supMenu: ''
        },
        {
          origin: true,
          uuid: Utils.getuuid(),
          label: 'tab2',
          type: 'SubTable',
          linkTab: '',
          supMenu: ''
        }
      ]
    }]
  }
  tabItems = [
    {
      type: 'tabs',
      label: CommonDict['header.menu.tab.subtable'],
      subType: 'SubTable',
    }
  ]
}
export default new CommonTableBaseData()
src/templates/zshare/editcomponent/index.jsx
@@ -12,7 +12,7 @@
    type: PropTypes.string,
    MenuID: PropTypes.any,
    config: PropTypes.object,
    thawButtons: PropTypes.array,
    thawButtons: PropTypes.any,
    refresh: PropTypes.func
  }
@@ -185,7 +185,7 @@
  render() {
    const menu = (
      <Menu onClick={this.handleMenuClick}>
        <Menu.Item key="thaw"><Icon type="unlock" />{this.props.dict['header.form.thawbutton']}</Menu.Item>
        {this.props.type !== 'TreePage' ? <Menu.Item key="thaw"><Icon type="unlock" />{this.props.dict['header.form.thawbutton']}</Menu.Item> : null}
        <Menu.Item key="paste"><Icon type="snippets" />{this.props.dict['header.form.paste']}</Menu.Item>
        {/* <Menu.Item key="replace"><Icon type="retweet" />替换</Menu.Item> */}
      </Menu>
src/templates/zshare/formconfig.jsx
@@ -119,7 +119,7 @@
      key: 'dataresource',
      label: '数据源',
      initVal: setting.dataresource || '',
      tooltip: '使用系统函数时,需填写数据源。',
      tooltip: '使用系统函数时,需填写数据源。注:数据权限替换符 $@ -> /* 或 \'\'、 @$ -> */ 或 \'\'',
      help: '数据ID:' + MenuID,
      required: false,
      readonly: false,
@@ -232,6 +232,282 @@
        { value: 'false', text: '不执行' }
      ]
    },
  ]
}
/**
 * @description 获取树形页面设置表单配置信息
 * @param {object} setting   // 菜单全局设置信息
 * @param {string} type      // 菜单类型
 */
export function getTreeSettingForm (setting, usefulFields = [], MenuID) {
  let str = '^(' + usefulFields.join('|') + ')'
  let _patten = new RegExp(str + formRule.func.innerPattern + '$', 'g')
  return [
    {
      type: 'text',
      key: 'tableName',
      label: '表名',
      initVal: setting.tableName || '',
      required: true,
      readonly: false,
      rules: [
        {
          max: formRule.input.max,
          message: formRule.input.message
        }
      ]
    },
    {
      type: 'text',
      key: 'title',
      label: '标题',
      initVal: setting.title || '',
      required: true,
      readonly: false,
      rules: [
        {
          max: formRule.input.max,
          message: formRule.input.message
        }
      ]
    },
    {
      type: 'radio',
      key: 'interType',
      label: Formdict['header.form.intertype'],
      initVal: setting.interType || 'inner',
      required: false,
      readonly: false,
      options: [
        { value: 'inner', text: Formdict['header.form.interface.inner'] },
        { value: 'outer', text: Formdict['header.form.interface.outer'] }
      ]
    },
    {
      type: 'radio',
      key: 'sysInterface',
      label: Formdict['header.form.sysInterface'],
      initVal: setting.sysInterface || 'false',
      required: false,
      readonly: false,
      options: [
        { value: 'true', text: Formdict['header.form.true'] },
        { value: 'false', text: Formdict['header.form.false'] }
      ]
    },
    {
      type: 'text',
      key: 'interface',
      label: Formdict['header.form.interface'],
      initVal: setting.sysInterface === 'true' ? (window.GLOB.mainSystemApi || '') : (setting.interface || ''),
      required: true,
      readonly: setting.sysInterface === 'true',
      rules: [
        {
          max: formRule.input.max,
          message: formRule.input.message
        }
      ]
    },
    {
      type: 'text',
      key: 'outerFunc',
      label: Formdict['header.form.outerFunc'],
      initVal: setting.outerFunc || '',
      required: false,
      readonly: false,
      rules: [
        {
          pattern: formRule.func.pattern,
          message: formRule.func.message
        }, {
          max: formRule.func.max,
          message: formRule.func.maxMessage
        }
      ]
    },
    {
      type: 'text',
      key: 'innerFunc',
      label: Formdict['header.form.innerFunc'],
      initVal: setting.innerFunc || '',
      tooltip: '开头可用字符:' + usefulFields.join(', '),
      placement: 'bottomLeft',
      required: false,
      readonly: false,
      rules: [
        {
          pattern: _patten,
          message: formRule.func.innerMessage
        }, {
          max: formRule.func.max,
          message: formRule.func.maxMessage
        }
      ]
    },
    {
      type: 'datasource',
      key: 'dataresource',
      label: '数据源',
      initVal: setting.dataresource || '',
      tooltip: '使用系统函数时,需填写数据源。注:数据权限替换符 $@ -> /* 或 \'\'、 @$ -> */ 或 \'\'',
      help: '数据ID:' + MenuID,
      required: false,
      readonly: false,
      rules: [
        {
          pattern: _patten,
          message: formRule.func.innerMessage
        }, {
          max: formRule.func.max,
          message: formRule.func.maxMessage
        }
      ]
    },
    {
      type: 'text',
      key: 'valueField',
      label: 'Value',
      initVal: setting.valueField || '',
      tooltip: '数据值字段。',
      required: true,
      readonly: false,
      rules: [
        {
          pattern: formRule.field.pattern,
          message: formRule.field.message
        }, {
          max: formRule.field.max,
          message: formRule.field.maxMessage
        }
      ]
    },
    {
      type: 'text',
      key: 'labelField',
      label: 'Label',
      initVal: setting.labelField || '',
      tooltip: '显示文字字段。',
      required: true,
      readonly: false,
      rules: [
        {
          pattern: formRule.field.pattern,
          message: formRule.field.message
        }, {
          max: formRule.field.max,
          message: formRule.field.maxMessage
        }
      ]
    },
    {
      type: 'text',
      key: 'parentField',
      label: 'Parent',
      initVal: setting.parentField || '',
      tooltip: '父级字段。',
      required: true,
      readonly: false,
      rules: [
        {
          pattern: formRule.field.pattern,
          message: formRule.field.message
        }, {
          max: formRule.field.max,
          message: formRule.field.maxMessage
        }
      ]
    },
    {
      type: 'text',
      key: 'order',
      label: '排序',
      initVal: setting.order || '',
      placeholder: 'ID asc, UID desc',
      required: true,
      readonly: false,
      rules: [
        {
          max: formRule.input.max,
          message: formRule.input.message
        }
      ]
    },
    {
      type: 'text',
      key: 'mark',
      label: '顶级标识',
      initVal: setting.mark || '',
      tooltip: '父级字段值与顶级标识(默认值为空)相同时,视为顶级节点。',
      required: false,
      readonly: false,
      rules: [
        {
          max: formRule.input.max,
          message: formRule.input.message
        }
      ]
    },
    {
      type: 'number',
      key: 'width',
      min: 2,
      max: 12,
      label: '宽度',
      tooltip: '每行分为24份,树形比例可设置为2-12(最大50%)',
      initVal: setting.width || 5,
      required: true
    },
    {
      type: 'radio',
      key: 'searchable',
      label: '搜索',
      initVal: setting.searchable || 'true',
      required: false,
      readonly: false,
      options: [
        { value: 'true', text: '显示' },
        { value: 'false', text: '隐藏' }
      ]
    },
    {
      type: 'radio',
      key: 'default',
      label: '默认sql',
      initVal: setting.default || 'true',
      required: false,
      readonly: false,
      options: [
        { value: 'true', text: '执行' },
        { value: 'false', text: '不执行' }
      ]
    },
    {
      type: 'radio',
      key: 'showIcon',
      label: '显示图标',
      initVal: setting.showIcon || 'false',
      required: false,
      readonly: false,
      options: [
        { value: 'true', text: Formdict['header.form.true'] },
        { value: 'false', text: Formdict['header.form.false'] }
      ]
    },
    {
      type: 'radio',
      key: 'showLine',
      label: '显示分割线',
      initVal: setting.showLine || 'false',
      required: false,
      readonly: false,
      options: [
        { value: 'true', text: Formdict['header.form.true'] },
        { value: 'false', text: Formdict['header.form.false'] }
      ]
    }
  ]
}
@@ -687,6 +963,20 @@
    })
  }
  let refresh = []
  if (type === 'subtable') { // 子表页面,可设置刷新主表及同级标签
    refresh.push({
      value: 'maingrid',
      text: Formdict['header.form.refresh.maingrid']
    }, {
      value: 'equaltab',
      text: Formdict['header.form.refresh.equaltab']
    }, {
      value: 'mainline',
      text: Formdict['header.form.refresh.mainline']
    })
  }
  return [
    {
      type: 'select',
@@ -928,7 +1218,8 @@
      }, {
        value: 'view',
        text: Formdict['header.form.refresh.view']
      }]
      },
      ...refresh]
    },
    {
      type: 'select',
@@ -962,7 +1253,8 @@
      }, {
        value: 'view',
        text: Formdict['header.form.refresh.view']
      }]
      },
      ...refresh]
    },
    {
      type: 'select',
@@ -2142,13 +2434,24 @@
      required: false
    },
    {
      type: 'number',
      key: 'level',
      label: '显示级别',
      tooltip: '标签显示控制,选择指定级别时显示标签,级别为空时始终显示。',
      initVal: card.level,
      min: 1,
      max: 10,
      required: false,
      forbid: type !== 'TreePage',
    },
    {
      type: 'radio',
      key: 'searchPass',
      label: '主表搜索',
      initVal: card.searchPass || 'false',
      tooltip: '使用主表搜索条件时,主表的搜索条件会传入子表中。',
      required: false,
      forbid: type !== 'main',
      forbid: type !== 'CommonTable',
      options: [{
        value: 'true',
        text: '使用'
src/utils/option.js
@@ -62,6 +62,13 @@
    isSubtable: true
  },
  {
    title: '树形页面',
    type: 'TreePage',
    url: mainsubtable,
    baseconfig: '',
    isSystem: true
  },
  {
    title: '角色权限分配',
    type: 'RolePermission',
    url: rolemanage,
src/utils/utils.js
@@ -67,10 +67,10 @@
      {key: 'alter', reg: /(^|\s)alter\s/ig},
      {key: 'truncate', reg: /(^|\s)truncate\s/ig},
      {key: 'if', reg: /(^|\s)if\s/ig},
      {key: 'exec', reg: /exec/ig},
      {key: 'OBJECT', reg: /object/ig},
      {key: 'sys.', reg: /sys\./ig},
      {key: 'kill', reg: /kill/ig}
      {key: 'exec', reg: /(^|\s)exec(\s|\()/ig},
      {key: 'OBJECT', reg: /(^|\s)object(\s|\()/ig},
      {key: 'sys.', reg: /(^|\s)sys\./ig},
      {key: 'kill', reg: /(^|\s)kill\s/ig}
    ]
    if (type === 'customscript') {