king
2020-08-29 000ff61dd8a88eb875048e6a3deca8679d75df18
2020-08-29
15个文件已修改
4个文件已添加
1056 ■■■■ 已修改文件
src/assets/mobimg/line.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/mobimg/line1.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/sidemenu/index.jsx 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/tabview/index.jsx 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/index.jsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/chartcompile/index.scss 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/index.jsx 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/components/chart/antv-bar/index.scss 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/datasourcecomponent/verifycard/settingform/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/header/index.jsx 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menuform/index.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/menushell/card.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/modelsource/option.jsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 630 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.scss 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/cardcomponent/index.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/option.js 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.jsx 122 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/mobimg/line.png
src/assets/mobimg/line1.png
src/components/sidemenu/index.jsx
@@ -130,7 +130,7 @@
              try {
                _tmenu.PageParam = JSON.parse(child.PageParam)
              } catch (e) {
                _tmenu.PageParam = {}
                _tmenu.PageParam = {OpenType: 'newtab'}
              }
              _tmenu.type = _tmenu.PageParam.Template || _tmenu.type
            } else {
@@ -142,6 +142,7 @@
            _tmenu.MenuNo = child.MenuNo
            _tmenu.MenuName = child.MenuName
            _tmenu.text = child.MenuName
            _tmenu.OpenType = _tmenu.PageParam ? _tmenu.PageParam.OpenType : 'newtab'
            return _tmenu
          })
        }
@@ -204,9 +205,11 @@
    }
    
    let menu = JSON.parse(e.target.dataset.item)
    if (menu.Ot === 'NewPage') {
    if (menu.OpenType === 'newpage') {
      e.preventDefault()
      window.open(menu.src)
    } else if (menu.Ot === 'blank') {
    } else if (menu.OpenType === 'blank') {
      menu.selected = true
      this.props.modifyTabview([menu])
      e.preventDefault()
src/components/tabview/index.jsx
@@ -18,6 +18,7 @@
import './index.scss'
const Home = asyncComponent(() => import('@/tabviews/home'))
const CustomPage = asyncComponent(() => import('@/tabviews/custom'))
const CommonTable = asyncComponent(() => import('@/tabviews/commontable'))
const CalendarPage = asyncComponent(() => import('@/tabviews/calendar'))
const TreePage = asyncComponent(() => import('@/tabviews/treepage'))
@@ -104,6 +105,8 @@
      return (<Home MenuNo={view.MenuNo} MenuID={view.MenuID} MenuName={view.MenuName} key={view.MenuID}/>)
    } else if (view.type === 'CommonTable' || view.type === 'ManageTable') {
      return (<CommonTable MenuNo={view.MenuNo} MenuID={view.MenuID} MenuName={view.MenuName} key={view.MenuID} param={view.param}/>)
    } else if (view.type === 'CustomPage') {
      return (<CustomPage 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} param={view.param}/>)
    } else if (view.type === 'CalendarPage') {
@@ -196,6 +199,15 @@
    }
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  render () {
    const { tabviews } = this.state
    let view = tabviews.filter(tab => tab.selected)[0]
@@ -213,7 +225,7 @@
                    <Tabs.TabPane
                      tab={
                        <span className="tab-control">
                          {['CommonTable', 'FormTab', 'TreePage', 'CalendarPage'].includes(view.type) ?
                          {['CommonTable', 'FormTab', 'TreePage', 'CalendarPage', 'CustomPage'].includes(view.type) ?
                            <Icon type="redo" onClick={(e) => {this.refreshTabview(e, view)}}/> : null
                          }
                          <span className="tab-name" onClick={(e) => {this.changeTab(e, view)}}>
src/menu/components/chart/antv-bar/chartcompile/formconfig.jsx
@@ -33,6 +33,9 @@
    ]
  }
  let xfields = columns.filter(item => /^Nvarchar/ig.test(item.datatype))
  let yfields = columns.filter(item => /^(Int|Decimal)/ig.test(item.datatype))
  return [
    {
      type: 'radio',
@@ -52,7 +55,7 @@
      label: 'X-轴',
      initVal: card.Xaxis || '',
      required: true,
      options: columns.filter(col => col.type === 'text')
      options: xfields
    },
    {
      type: 'select',
@@ -62,7 +65,7 @@
      multi: true, // 多选
      hidden: card.datatype === 'statistics',
      required: true,
      options: columns.filter(col => col.type === 'number')
      options: yfields
    },
    {
      type: 'select',
@@ -71,7 +74,7 @@
      initVal: card.InfoType || '',
      hidden: card.datatype !== 'statistics',
      required: true,
      options: columns.filter(col => col.type === 'text')
      options: xfields
    },
    {
      type: 'select',
@@ -80,7 +83,7 @@
      initVal: card.InfoValue || '',
      hidden: card.datatype !== 'statistics',
      required: true,
      options: columns.filter(col => col.type === 'number')
      options: yfields
    },
    {
      type: 'select',
src/menu/components/chart/antv-bar/chartcompile/index.jsx
@@ -23,7 +23,6 @@
    disabled: true,
    plot: null,
    formlist: null,
    datatype: null,
    fieldName: null,
    colorOptions: fromJS(minkeColorSystem).toJS().map(option => {
      option.children = option.children.map(cell => {
@@ -78,6 +77,7 @@
    this.setState({
      visible: true,
      view: 'normal',
      disabled: config.plot.datatype === 'statistics',
      fieldName: fieldName,
      plot: fromJS(config.plot).toJS(),
      formlist: getBarOrLineChartOptionForm(config.plot, config.columns, config.setting)
@@ -90,7 +90,7 @@
    if (key === 'datatype') {
      this.setState({
        datatype: val,
        disabled: val === 'statistics',
        formlist: formlist.map(item => {
          if (['Yaxis'].includes(item.key)) {
            item.hidden = val === 'statistics'
@@ -336,6 +336,7 @@
  }
  onSubmit = () => {
    const { config } = this.props
    const { plot, view, datatype } = this.state
    if (view !== 'custom') {
@@ -360,7 +361,7 @@
            visible: false
          })
          this.props.plotchange(_plot)
          this.props.plotchange({...config, plot: _plot})
        }
      })
    } else {
@@ -400,12 +401,11 @@
    }
  }
  changeView = () => {
  changeTab = (tab) => {
    const { config } = this.props
    const { plot, view } = this.state
    let _view = view === 'normal' ? 'custom' : 'normal'
    const { plot } = this.state
    if (_view === 'custom') {
    if (tab === 'custom') {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          let _plot = {...plot, ...values}
@@ -426,7 +426,7 @@
          this.setState({
            enabled: _plot.enabled || 'false',
            plot: _plot,
            view: _view
            view: tab
          })
        }
      })
@@ -462,15 +462,12 @@
          this.setState({
            plot: _plot,
            view: _view,
            view: tab,
            formlist: getBarOrLineChartOptionForm(_plot, config.columns)
          })
        }
      })
    }
  }
  changeTab = () => {
  }
@@ -499,32 +496,22 @@
          maskClosable={false}
          okText={dict['model.submit']}
          cancelText={dict['model.cancel']}
          onOk={this.verifySubmit}
          onOk={this.onSubmit}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <Tabs activeKey={view} className="menu-chart-edit-box" onChange={this.changeTab}>
            <TabPane tab="基础设置" key="normal">
              <Form {...formItemLayout}>
              {view === 'normal' ? <Form {...formItemLayout}>
                <Row gutter={16}>{this.getFields()}</Row>
              </Form>
              </Form> : null}
            </TabPane>
            <TabPane tab="字段集" disabled={disabled} key="custom">
            <TabPane tab="自定义设置" disabled={disabled} key="custom">
              {view === 'custom' ? <Form {...formItemLayout}>
                <Row gutter={16}>{this.getCustomFields()}</Row>
              </Form> : null}
            </TabPane>
          </Tabs>
          {/* {view !== 'custom' ? <Form {...formItemLayout} className="base-setting">
            <Row gutter={16}>{this.getFields()}</Row>
            {datatype === 'query' ? <Row gutter={16}>
              <Button onClick={this.changeView} style={{border: 0, boxShadow: 'unset',float: 'right', color: '#1890ff', marginRight: 12, cursor: 'pointer'}}>自定义设置<Icon style={{marginLeft: 5}} type="right" /></Button>
            </Row> : null}
          </Form> : null}
          {view === 'custom' ? <Form {...formItemLayout} id="chart-custom-drawer-form" className="mingke-table">
            <Row gutter={16} style={{minHeight: 'calc(100vh - 180px)'}}>{this.getCustomFields()}</Row>
            <Row gutter={16}>
              <Button onClick={this.changeView} style={{border: 0, boxShadow: 'unset', color: '#1890ff', marginRight: 12, cursor: 'pointer'}}><Icon style={{marginRight: 5}} type="left" />基本设置</Button>
            </Row>
          </Form> : null} */}
        </Modal>
      </div>
    );
src/menu/components/chart/antv-bar/chartcompile/index.scss
@@ -10,16 +10,20 @@
  }
}
.menu-chart-edit-modal {
  .ant-modal-body {
    padding-top: 10px;
    .menu-chart-edit-box {
      .anticon-question-circle {
        color: #c49f47;
        position: relative;
        left: -3px;
      }
      .ant-input-number {
        width: 100%;
  .ant-modal {
    top: 50px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
      padding-top: 10px;
      .menu-chart-edit-box {
        .anticon-question-circle {
          color: #c49f47;
          position: relative;
          left: -3px;
        }
        .ant-input-number {
          width: 100%;
        }
      }
    }
  }
src/menu/components/chart/antv-bar/index.jsx
@@ -31,13 +31,14 @@
  }
  UNSAFE_componentWillMount () {
    const { card } = this.props
    const { card, config } = this.props
    if (card.isNew) {
      let _plot = {
        chartType: card.type, // 图表类型
        enabled: 'false',     // 是否使用自定义设置
        datatype: 'query',    // 数据类型查询或统计
        customs: []
      }
      if (card.subtype === 'bar') {
@@ -46,13 +47,32 @@
      } else if (card.subtype === 'bar1') {
        _plot.coordinate = 'angle'
        _plot.transpose = 'true'
      } else if (card.subtype === 'line') {
        _plot.shape = 'smooth'
      } else if (card.subtype === 'line1') {
        _plot.shape = 'hv'
      }
      let name = ''
      let names = {
        bar: '柱状图',
        line: '折线图',
      }
      let i = 1
      while (!name) {
        let _name = names[card.type] + i
        if (config.components.filter(com => com.setting && com.setting.name === _name).length === 0) {
          name = _name
        }
        i++
      }
      let _card = {
        uuid: card.uuid,
        type: card.type,
        subtype: card.subtype,
        setting: {span: 12, height: 400},
        setting: {span: 12, height: 400, name},
        columns: [],
        scripts: [],
        search: [],
@@ -76,9 +96,7 @@
  UNSAFE_componentWillReceiveProps (nextProps) {
    if (!is(fromJS(this.props.plot), fromJS(nextProps.plot))) {
      this.setState({}, () => {
        this.viewrender()
      })
    }
  }
@@ -117,10 +135,11 @@
  }
  linerender = () => {
    const { plot, config } = this.props
    const { card } = this.state
    let plot = {...card.plot, height: card.setting.height - 70}
    let transfield = {}
    config.columns.forEach(col => {
    card.columns.forEach(col => {
      if (col.field) {
        transfield[col.field] = col.label
      }
@@ -153,7 +172,7 @@
      }
      const chart = new Chart({
        container: plot.uuid,
        container: card.uuid,
        autoFit: true,
        height: plot.height || 400
      })
@@ -509,8 +528,24 @@
  }
  updateComponent = (component) => {
    const card = fromJS(this.state.card).toJS()
    let refresh = false
    if (card.setting.span !== component.setting.span || card.setting.height !== component.setting.height || !is(fromJS(component.plot), fromJS(card.plot))) {
      let _element = document.getElementById(card.uuid)
      if (_element) {
        _element.innerHTML = ''
      }
      refresh = true
    }
    this.setState({
      card: component
    }, () => {
      if (refresh) {
        setTimeout(() => {
          this.viewrender()
        }, 100)
      }
    })
    this.props.updateConfig(component)
  }
@@ -520,7 +555,7 @@
    const { config } = this.props
    return (
      <div className="line-chart-edit-box" style={{height: card.setting.height || 400}}>
      <div className="menu-line-chart-edit-box" style={{height: card.setting.height || 400}}>
        <SettingComponent
          config={{...card, tables: config.tables}}
          MenuID={config.uuid}
src/menu/components/chart/antv-bar/index.scss
@@ -1,6 +1,6 @@
.line-chart-edit-box {
.menu-line-chart-edit-box {
  position: relative;
  margin-bottom: 30px;
  // margin-bottom: 0px;
  border: 1px solid #e8e8e8;
  
  .canvas {
src/menu/datasourcecomponent/verifycard/settingform/index.jsx
@@ -286,7 +286,7 @@
              }>
                {getFieldDecorator('height', {
                  initialValue: setting.height || 400
                })(<InputNumber min={100} max={1500} precision={0} />)}
                })(<InputNumber min={150} max={1500} precision={0} />)}
              </Form.Item>
            </Col>
            {interType === 'inner' ? <Col span={8}>
src/menu/header/index.jsx
@@ -3,7 +3,7 @@
import { is, fromJS } from 'immutable'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {Dropdown, Menu, Icon, Modal } from 'antd'
// import { Icon, Modal } from 'antd'
import { logout } from '@/store/action'
import zhCN from '@/locales/zh-CN/mob.js'
@@ -11,7 +11,7 @@
import avatar from '@/assets/img/avatar.jpg'
import './index.scss'
const { confirm } = Modal
// const { confirm } = Modal
class MobHeader extends Component {
  static propTpyes = {
@@ -27,22 +27,22 @@
    userName: sessionStorage.getItem('CloudUserName')
  }
  logout = () => {
    // 退出登录
    let _this = this
    confirm({
      title: this.state.dict['mob.logout.hint'],
      content: '',
      okText: this.state.dict['mob.confirm'],
      cancelText: this.state.dict['mob.cancel'],
      onOk() {
        sessionStorage.clear()
        _this.props.logout()
        _this.props.history.replace('/login')
      },
      onCancel() {}
    })
  }
  // logout = () => {
  //   // 退出登录
  //   let _this = this
  //   confirm({
  //     title: this.state.dict['mob.logout.hint'],
  //     content: '',
  //     okText: this.state.dict['mob.confirm'],
  //     cancelText: this.state.dict['mob.cancel'],
  //     onOk() {
  //       sessionStorage.clear()
  //       _this.props.logout()
  //       _this.props.history.replace('/login')
  //     },
  //     onCancel() {}
  //   })
  // }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
@@ -75,18 +75,18 @@
    return (
      <header className="menu-header-container">
        <div className="header-logo"><img src={this.state.logourl} alt=""/></div>
        <Dropdown className="header-setting" overlay={
        {/* <Dropdown className="header-setting" overlay={
          <Menu>
            <Menu.Item key="2" onClick={this.logout}>{this.state.dict['mob.logout']}</Menu.Item>
          </Menu>
        }>
          <div>
        }> */}
          <div className="header-setting">
            <img src={this.state.avatar} alt=""/>
            <span>
              <span className="username">{this.state.userName}</span> <Icon type="down" />
              <span className="username">{this.state.userName}</span>{/* <Icon type="down" /> */}
            </span>
          </div>
        </Dropdown>
        {/* </Dropdown> */}
      </header>
    )
  }
src/menu/menuform/index.jsx
@@ -58,7 +58,7 @@
          }
        })
        this.props.initMenuList(fromJS(menulist).toJS())
        this.props.initMenuList({fstMenuList: fromJS(menulist).toJS(), fstMenuId: result.FstIDSeleted})
        this.setState({
          fstMenuId: result.FstIDSeleted,
@@ -105,8 +105,8 @@
      }, () => {
        let _id = smenulist[0] ? smenulist[0].value : ''
        this.props.form.setFieldsValue({parentId: _id})
        this.props.updateConfig({...config, fstMenuId: value, parentId: _id})
      })
      this.props.updateConfig({...config, fstMenuId: value})
    } else if (key === 'parentId') {
      this.props.updateConfig({...config, parentId: value})
    }
src/menu/menushell/card.jsx
@@ -38,7 +38,7 @@
  }
  const getCardComponent = () => {
    if (card.type === 'bar') {
    if (card.type === 'bar' || card.type === 'line') {
      return (<AntvBar config={config} card={card} triggerEdit={editCard} editId={editId} updateConfig={updateConfig} />)
    }
  }
src/menu/modelsource/option.jsx
@@ -2,11 +2,15 @@
// import enUS from '@/locales/en-US/mob.js'
import bar from '@/assets/mobimg/bar.png'
import bar1 from '@/assets/mobimg/bar1.png'
import line from '@/assets/mobimg/line.png'
import line1 from '@/assets/mobimg/line1.png'
// const _dict =  sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
// 组件配置信息
export const menuOptions = [
  { type: 'menu', url: line, component: 'line', subtype: 'line' },
  { type: 'menu', url: line1, component: 'line', subtype: 'line1' },
  { type: 'menu', url: bar, component: 'bar', subtype: 'bar' },
  { type: 'menu', url: bar1, component: 'bar', subtype: 'bar1' },
]
src/tabviews/custom/index.jsx
New file
@@ -0,0 +1,630 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { notification, Spin, Modal, Button, message, Tree, Typography } from 'antd'
import moment from 'moment'
import Api from '@/api'
import options from '@/store/options.js'
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import asyncSpinComponent from '@/utils/asyncSpinComponent'
import { refreshTabView } from '@/store/action'
import MainSearch from '@/tabviews/zshare/topSearch'
import NotFount from '@/components/404'
import './index.scss'
// 通用组件
const CalendarComponent = asyncSpinComponent(() => import('@/tabviews/zshare/calendar'))
const SubTabTable = asyncSpinComponent(() => import('@/tabviews/subtabtable'))
const { TreeNode } = Tree
const { Paragraph } = Typography
class NormalTable extends Component {
  static propTpyes = {
    param: PropTypes.any,        // 其他页面传递的参数
    MenuID: PropTypes.string,    // 菜单Id
    MenuNo: PropTypes.string,    // 菜单参数
    MenuName: PropTypes.string   // 菜单名称
  }
  state = {
    dict: sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    ContainerId: Utils.getuuid(), // 菜单外层html Id
    BID: null,            // 页面跳转时携带ID
    loadingview: true,    // 页面加载中
    viewlost: false,      // 页面丢失:1、未获取到配置-页面丢失;2、页面未启用
    lostmsg: '',          // 页面丢失时的提示信息
    config: {},           // 页面配置信息,包括按钮、搜索、显示列、标签等
    userConfig: null,     // 用户自定义设置
    searchlist: null,     // 搜索条件
    arr_field: '',        // 使用 sPC_Get_TableData 时的查询字段集
    setting: null,        // 页面全局设置:数据源、按钮及显示列固定、主键等
    data: null,           // 列表数据集
    loading: false,       // 列表数据加载中
    search: '',           // 搜索条件数组,使用时需分场景处理
    visible: false,       // 标签页控制
    triggerTime: '',      // 点击时间
    treevisible: false,   // 菜单结构树弹框显示隐藏控制
    calendarYear: moment().format('YYYY') // 日历年份
  }
  /**
   * @description 获取页面配置信息
   */
  async loadconfig () {
    const { permAction, param } = this.props
    let _param = {
      func: 'sPC_Get_LongParam',
      MenuID: this.props.MenuID
    }
    let result = await Api.getCacheConfig(_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 = ''
      }
      // HS不使用自定义设置
      if (result.LongParamUser && this.props.menuType !== 'HS') {
        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
      }
      // 权限过滤
      if (this.props.menuType !== 'HS') {
        if (config.tab && !permAction[config.tab.linkTab]) {
          config.tab = null
        }
      }
      if (_curUserConfig) {
        config.setting = {...config.setting, ..._curUserConfig.setting}
        config.easyCode = _curUserConfig.easyCode || config.easyCode || ''
      }
      // 字段权限黑名单、必填、字段透视
      let valid = true
      config.search = config.search.map(item => {
        item.oriInitval = item.initval
        if (['text', 'select', 'link'].includes(item.type) && param) {
          if (param.searchkey === item.field) {
            item.initval = param.searchval
          } else if (param.BID && item.field.toLowerCase() === 'bid') {
            item.initval = param.BID
          } else if (param.data && param.data[item.field]) {
            item.initval = param.data[item.field]
          }
        }
        if (item.required === 'true' && !item.initval) {
          valid = false
        }
        if (!item.blacklist || item.blacklist.length === 0) return item
        let _black = item.blacklist.filter(v => {
          return this.props.permRoles.indexOf(v) !== -1
        })
        if (_black.length > 0) {
          item.Hide = 'true'
        }
        return item
      })
      this.setState({
        BID: param && param.BID ? param.BID : '',
        loadingview: false,
        config: config,
        userConfig: userConfig,
        setting: config.setting,
        searchlist: config.search,
        arr_field: config.columns.map(item => item.field).join(','),
        search: Utils.initMainSearch(config.search) // 搜索条件初始化(含有时间格式,需要转化)
      }, () => {
        if (config.setting.onload !== 'false' && valid) { // 初始化可加载
          this.loadmaindata()
        }
      })
    } else {
      this.setState({
        loadingview: false,
        viewlost: true
      })
      notification.warning({
        top: 92,
        message: result.message,
        duration: 5
      })
    }
  }
  /**
   * @description 主表数据加载
   */
  async loadmaindata () {
    const { setting, search, BID } = this.state
    let param = ''
    let requireFields = search.filter(item => item.required && (!item.value || item.value.length === 0))
    if (requireFields.length > 0) {
      let labels = requireFields.map(item => item.label)
      labels = Array.from(new Set(labels))
      notification.warning({
        top: 92,
        message: this.state.dict['form.required.input'] + labels.join('、') + ' !',
        duration: 3
      })
      return
    }
    this.setState({
      loading: true
    })
    if (setting.interType !== 'inner' || (setting.interType === 'inner' && setting.innerFunc)) {
      param = this.getCustomParam()
    } else {
      param = this.getDefaultParam()
    }
    if (BID) {
      param.BID = BID
    }
    // 数据管理权限
    if (this.props.dataManager) {
      param.dataM = 'Y'
    }
    let result = await Api.genericInterface(param)
    if (result.status) {
      this.setState({
        data: result.data.map((item, index) => {
          item.key = index
          return item
        }),
        loading: false
      })
    } else {
      this.setState({
        loading: false
      })
      notification.error({
        top: 92,
        message: result.message,
        duration: 10
      })
    }
  }
  /**
   * @description 获取用户自定义存储过程传参
   */
  getCustomParam = () => {
    const { search, setting, calendarYear, config } = this.state
    let _search = Utils.formatCustomMainSearch(search)
    let param = {
      ..._search
    }
    if (config.calendar.refresh === 'true') {
      param.calendarDate = calendarYear
    }
    if (setting.interType === 'inner') {
      param.func = setting.innerFunc
    } else {
      if (this.props.menuType === 'HS') {
        if (setting.sysInterface === 'true' && options.cloudServiceApi) {
          param.rduri = options.cloudServiceApi
        } else if (setting.sysInterface !== 'true') {
          param.rduri = setting.interface
        }
      } 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 { arr_field, search, setting, config, calendarYear } = this.state
    let _search = Utils.joinMainSearchkey(search)
    _search = _search ? 'where ' + _search : ''
    let param = {
      func: 'sPC_Get_TableData',
      obj_name: 'data',
      arr_field: arr_field,
      custom_script: setting.customScript || '',
      default_sql: setting.default || 'true'
    }
    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 regoptions = null
    if (setting.queryType === 'statistics' || param.custom_script) {
      let allSearch = Utils.getAllSearchOptions(search)
      regoptions = allSearch.map(item => {
        return {
          reg: new RegExp('@' + item.key + '@', 'ig'),
          value: `'${item.value}'`
        }
      })
    }
    if (config.calendar.refresh === 'true' && regoptions) {
      regoptions.push({
        reg: new RegExp('@calendarDate@', 'ig'),
        value: `${calendarYear}-01-01 00:00:00.000`
      })
      regoptions.push({
        reg: new RegExp('@calendarDate1@', 'ig'),
        value: `${calendarYear}-12-31 23:59:59.999`
      })
    }
    if (setting.queryType === 'statistics' && setting.default !== 'false') { // 统计数据源,内容替换
      regoptions.forEach(item => {
        _dataresource = _dataresource.replace(item.reg, item.value)
      })
      _search = ''
    }
    let LText = ''
    if (setting.default !== 'false') {
      LText = `select ${arr_field} from ${_dataresource} ${_search}`
    }
    if (param.custom_script) {
      regoptions.forEach(item => {
        param.custom_script = param.custom_script.replace(item.reg, item.value)
      })
      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@
        `
      }
    }
    // 测试系统打印查询语句
    if ((options.sysType === 'local' && !window.GLOB.systemType) || window.debugger === true) {
      param.custom_script &&  console.log(`${LText ? '' : '/*不执行默认sql*/\n'}${param.custom_script}`)
      LText &&  console.log(LText)
    }
    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 = ''
    if (this.props.menuType === 'HS') { // 云端数据验证
      param.open_key = Utils.encrypt(param.secretkey, param.timestamp, true)
    }
    return param
  }
  /**
   * @description 搜索条件改变时,重置表格数据
   * 含有初始不加载的页面,修改设置
   */
  refreshbysearch = (searches) => {
    const { setting } = this.state
    if (setting.onload === 'false') {
      this.setState({
        search: searches,
        setting: {...setting, onload: 'true'}
      }, () => {
        this.loadmaindata()
      })
    } else {
      this.setState({
        search: searches
      }, () => {
        this.loadmaindata()
      })
    }
  }
  /**
   * @description 页面刷新,重新获取配置
   */
  reloadview = () => {
    this.setState({ loadingview: true, viewlost: false, lostmsg: '', data: null, loading: false, search: ''
    }, () => {
      this.loadconfig()
    })
  }
  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} />
    })
  }
  UNSAFE_componentWillMount () {
    // 组件加载时,获取菜单数据
    this.loadconfig()
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (nextProps.refreshTab && nextProps.refreshTab.MenuID === this.props.MenuID) {
      if (nextProps.refreshTab.position === 'grid') {
        this.loadmaindata()
      } else if (nextProps.refreshTab.position === 'view') {
        this.reloadview()
      }
      this.props.refreshTabView('')
    } else if (nextProps.param && !is(fromJS(this.props.param), fromJS(nextProps.param))) {
      let search = this.state.search.map(item => {
        if (item.type === 'text' && item.key === nextProps.param.searchkey) {
          item.value = nextProps.param.searchval
        }
        return item
      })
      this.refreshbysearch(search)
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新,清除快捷键设置
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  changeDate = (value) => {
    this.setState({calendarYear: value}, () => {
      this.loadmaindata()
    })
  }
  triggerDate = (item) => {
    const { config } = this.state
    if (!config.tab) return
    this.setState({
      visible: true,
      triggerTime: item.time.substr(0, 4) + '-' + item.time.substr(4, 2) + '-' + item.time.substr(6, 2)
    })
  }
  closeTab = () => {
    this.setState({
      visible: false,
      triggerTime: ''
    })
  }
  render() {
    const { BID, searchlist, loadingview, viewlost, config, loading, data, triggerTime } = this.state
    return (
      <div className="calendar-page" id={this.state.ContainerId}>
        {loadingview && <Spin size="large" />}
        {searchlist && searchlist.length > 0 ?
          <MainSearch
            BID={BID}
            dict={this.state.dict}
            searchlist={searchlist}
            menuType={this.props.menuType}
            dataManager={this.props.dataManager}
            refreshdata={this.refreshbysearch}
          /> : null
        }
        {config && config.calendar ? <CalendarComponent calendar={config.calendar} loading={loading} data={data} triggerDate={this.triggerDate} changeDate={this.changeDate}/> : null}
        {options.sysType !== 'cloud' ? <Button
          icon="copy"
          shape="circle"
          className="common-table-copy"
          onClick={this.handleviewconfig}
        /> : null}
        <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
          title={config.tab ? config.tab.label : ''}
          width={'80vw'}
          maskClosable={false}
          visible={this.state.visible}
          onCancel={this.closeTab}
          footer={[
            <Button key="close" onClick={this.closeTab}>{this.state.dict['main.close']}</Button>
          ]}
          destroyOnClose
        >
          {config.tab ? <SubTabTable
            type="calendar"
            BID={triggerTime}
            Tab={config.tab}
            SupMenuID={this.props.MenuID}
            MenuID={config.tab.linkTab}
            refreshSupView={() => this.loadmaindata()}
            closeModalView={this.closeTab}
          /> : null}
        </Modal>
        {viewlost ? <NotFount msg={this.state.lostmsg} /> : null}
      </div>
    )
  }
}
const mapStateToProps = (state) => {
  return {
    menuType: state.editLevel,
    tabviews: state.tabviews,
    refreshTab: state.refreshTab,
    permAction: state.permAction,
    permRoles: state.permRoles,
    dataManager: state.dataManager
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    refreshTabView: (refreshTab) => dispatch(refreshTabView(refreshTab))
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(NormalTable)
src/tabviews/custom/index.scss
New file
@@ -0,0 +1,58 @@
.calendar-page {
  position: relative;
  min-height: calc(100vh - 94px);
  padding-top: 16px;
  padding-bottom: 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);
  }
  .common-table-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;
    }
  }
}
src/tabviews/zshare/cardcomponent/index.scss
@@ -23,16 +23,6 @@
    color: #40a9ff;
  }
  .chart-card-box:hover {
    .ant-card.chart-card {
      .ant-card-head {
        .ant-card-extra {
          opacity: 1;
        }
      }
    }
  }
  .chart-card-box:not(.ant-col) {
    display: inline-block;
    margin-bottom: 20px;
@@ -130,8 +120,6 @@
      }
      .ant-card-extra {
        padding: 0px;
        opacity: 0;
        transition: opacity 0.3s;
      }
    }
    .ant-card-actions {
src/utils/option.js
@@ -4,7 +4,7 @@
import mainsubtable from '@/assets/img/mainsubtable.jpg'
import treepage from '@/assets/img/treepage.jpg'
import calendar from '@/assets/img/calendar.jpg'
// import customImg from '@/assets/img/custom.jpg'
import customImg from '@/assets/img/custom.jpg'
import rolemanage from '@/assets/img/rolemanage.jpg'
const _dict =  sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
@@ -78,13 +78,13 @@
    baseconfig: '',
    isSystem: true
  },
  // {
  //   title: '自定义',
  //   type: 'CustomPage',
  //   url: customImg,
  //   baseconfig: '',
  //   isSystem: true
  // },
  {
    title: '自定义',
    type: 'CustomPage',
    url: customImg,
    baseconfig: '',
    isSystem: true
  },
  {
    title: '角色权限分配',
    type: 'RolePermission',
src/views/menudesign/index.jsx
@@ -26,14 +26,16 @@
// const DataSource = asyncComponent(() => import('@/mob/datasource'))
const TableComponent = asyncComponent(() => import('@/templates/sharecomponent/tablecomponent'))
sessionStorage.setItem('isEditState', 'true')
class Mobile extends Component {
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    MenuId: this.props.match.params.MenuId,
    tableFields: [],
    activeKey: 'basedata',
    menuloading: false,
    oriConfig: null,
    parentId: '',
    openEdition: '',
    config: null,
    editElem: null
@@ -79,7 +81,20 @@
  }
  submitConfig = () => {
    const { config, openEdition, parentId } = this.state
    const { config, openEdition } = this.state
    if (!config.MenuName || !config.MenuNo || !config.fstMenuId || !config.parentId) {
      notification.warning({
        top: 92,
        message: '请完善菜单基本信息!',
        duration: 5
      })
      return
    }
    if (config.enabled && this.verifyConfig()) {
      config.enabled = false
    }
    let _config = fromJS(config).toJS()
    delete _config.fstMenuList
@@ -87,28 +102,36 @@
    delete _config.sysRoles
    delete _config.tableFields
    let funcs = []
    _config.components.forEach(component => {
      if (component.setting && component.setting.innerFunc) {
        funcs.push(`select '${_config.uuid}' as MenuID,'${component.setting.innerFunc}' as ProcName,'${component.setting.name}' as MenuName`)
      }
      if (component.action) {
        component.action.forEach(item => {
          if (!item.innerFunc) return
          funcs.push(`select '${_config.uuid}' as MenuID,'${item.innerFunc}' as ProcName,'${item.label}' as MenuName`)
        })
      }
    })
    let param = {
      func: 'sPC_TrdMenu_AddUpt',
      ParentID: parentId,
      MenuID: config.uuid,
      MenuNo: config.MenuNo,
      EasyCode: '',
      Template: '',
      MenuName: '',
      PageParam: '',
      FstID: _config.fstMenuId,
      SndID: _config.parentId,
      ParentID: _config.parentId,
      MenuID: _config.uuid,
      MenuNo: _config.MenuNo,
      EasyCode: _config.easyCode,
      Template: 'CustomPage',
      MenuName: _config.MenuName,
      PageParam: JSON.stringify({Template: 'CustomPage', OpenType: 'newtab'}),
      LongParam: window.btoa(window.encodeURIComponent(JSON.stringify(_config))),
      // 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`),
      TypeCharOne: 'mob'
      LText: funcs.join(' union all '),
      LTexttb: '', // 表名
    }
    let _LText = ''
    // _LText = _LText.join(' union all ')
    let _LTexttb = ''
    // _LTexttb = _LTexttb.join(' union all ')
    param.LText = Utils.formatOptions(_LText)
    param.LTexttb = Utils.formatOptions(_LTexttb)
    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)
@@ -116,13 +139,27 @@
      param.open_edition = openEdition
    }
    this.setState({
      menuloading: true
    })
    Api.getSystemConfig(param).then(response => {
      if (response.status) {
        this.setState({
          oriConfig: fromJS(config).toJS(),
          openEdition: response.open_edition || ''
          openEdition: response.open_edition || '',
          menuloading: false
        })
        notification.success({
          top: 92,
          message: '保存成功',
          duration: 2
        })
      } else {
        this.setState({
          openEdition: response.open_edition || '',
          menuloading: false
        })
        notification.warning({
          top: 92,
          message: response.message,
@@ -154,6 +191,7 @@
            version: 1.0,
            uuid: this.props.match.params.MenuId,
            MenuID: this.props.match.params.MenuId,
            parentId: this.props.match.params.ParentId,
            Template: 'CustomPage',
            easyCode: '',
            enabled: false,
@@ -162,7 +200,11 @@
            tables: [],
            components: []
          }
        } else {
          config.uuid = this.props.match.params.MenuId
          config.MenuID = this.props.match.params.MenuId
        }
        this.setState({
          oriConfig: config,
          config: fromJS(config).toJS(),
@@ -212,9 +254,9 @@
    })
  }
  initMenuList = (list) => {
  initMenuList = (msg) => {
    this.setState({
      config: {...this.state.config, fstMenuList: list}
      config: {...this.state.config, ...msg}
    })
  }
@@ -247,9 +289,43 @@
  onEnabledChange = () => {
    const { config } = this.state
    if (!config.enabled && this.verifyConfig(true)) {
      return
    }
    this.setState({
      config: {...config, enabled: !config.enabled}
    })
  }
  verifyConfig = (show) => {
    const { config } = this.state
    let error = ''
    if (!config.MenuName || !config.MenuNo || !config.fstMenuId || !config.parentId) {
      notification.warning({
        top: 92,
        message: '请完善菜单基本信息!',
        duration: 5
      })
      return 'false'
    }
    config.components.forEach(item => {
      if (!error && (!item.setting || !item.setting.dataresource)) {
        error = `组件《${item.setting.name}》未设置数据源`
      }
    })
    if (show && error) {
      notification.warning({
        top: 92,
        message: error,
        duration: 5
      })
    }
    return error
  }
  // 更新配置信息
@@ -322,7 +398,7 @@
                  <Button type="primary" onClick={this.submitConfig} loading={this.state.menuloading}>{dict['mob.save']}</Button>
                </div>
              } style={{ width: '100%' }}>
                {config ? <MenuShell config={config} handleList={this.updateConfig} deleteCard={this.deleteCard} /> : null}
                {config && config.components ? <MenuShell config={config} handleList={this.updateConfig} deleteCard={this.deleteCard} /> : null}
              </Card>
            </div>
          </div>