king
2020-08-15 41239717c4446af79268b968557274f88a0afaeb
2020-08-15
21个文件已修改
23个文件已添加
4188 ■■■■■ 已修改文件
public/options.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/index.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/commontable/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtable/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/subtabtable/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/treepage/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/calendar/index.jsx 171 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/calendar/index.scss 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/zshare/mutilform/index.jsx 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/calcomponent/calendarform/index.jsx 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/calcomponent/calendarform/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/calcomponent/index.jsx 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/calcomponent/index.scss 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/index.jsx 890 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/index.scss 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/source.jsx 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/tabcomponent/index.jsx 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/tabcomponent/index.scss 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/tabcomponent/tabform/index.jsx 278 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/calendarconfig/tabcomponent/tabform/index.scss 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/comtableconfig/index.jsx 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/menuconfig/editthdmenu/index.jsx 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/modalconfig/dragelement/card.jsx 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/dragaction/index.jsx 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/columnform/index.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/index.jsx 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/index.scss 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/columnform/index.jsx 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/columnform/index.scss 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/customscript/index.jsx 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/customscript/index.scss 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/index.jsx 524 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/index.scss 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/settingform/index.jsx 278 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/settingform/index.scss 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/settingform/utils.jsx 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/datasourcecomponent/verifycard/utils.jsx 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/tabscomponent/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/formconfig.jsx 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/modalform/index.jsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/option.js 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/utils.js 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login/index.scss 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/options.json
@@ -1,7 +1,7 @@
{
  "appId": "201912040924165801464FF1788654BC5AC73",
  "appkey": "20191106103859640976D6E924E464D029CF0",
  "mainSystemApi": "http://cloud.mk9h.cn/webapi/dostars",
  "mainSystemApi": "http://sso.mk9h.cn/cloud/webapi/dostars",
  "systemType": "",
  "lineColor": "",
  "filter": "false"
src/index.js
@@ -86,21 +86,28 @@
      if (config.mainSystemApi) {
        let systemApi = config.mainSystemApi
      
        if (/^(http|https):\/\//ig.test(systemApi)) {
          let _systemApi = /^(http|https):\/\/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}|(:[0-9]{1,4}))+\.?/ig.exec(systemApi)
        // if (/^(http|https):\/\//ig.test(systemApi)) {
        //   let _systemApi = /^(http|https):\/\/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}|(:[0-9]{1,4}))+\.?/ig.exec(systemApi)
      
          systemApi = _systemApi ? _systemApi[0] : ''
        } else {
        //   systemApi = _systemApi ? _systemApi[0] : ''
        // } else {
        //   systemApi = ''
        // }
        // // 业务系统连接云端时,格式化处理
        // if (systemApi && systemApi === /^(http|https):\/\/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}|(:[0-9]{1,4}))+\.?/ig.exec(options.cloudServiceApi)[0]) {
        //   window.GLOB.dataFormat = true
        // }
        // if (systemApi) {
        //   systemApi = systemApi + '/webapi/dostars'
        // }
        // 业务系统不允许连接云端,业务系统连接sso.mk9h.cn时,数据虚化处理
        if (systemApi && systemApi.indexOf('cloud.mk9h.cn') > -1) {
          systemApi = ''
        }
        // 业务系统连接云端时,格式化处理
        if (systemApi && systemApi === /^(http|https):\/\/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}|(:[0-9]{1,4}))+\.?/ig.exec(options.cloudServiceApi)[0]) {
        } else if (systemApi && systemApi.indexOf('sso.mk9h.cn') > -1) {
          window.GLOB.dataFormat = true
        }
        if (systemApi) {
          systemApi = systemApi + '/webapi/dostars'
        }
      
        window.GLOB.mainSystemApi = systemApi
src/tabviews/commontable/index.jsx
@@ -735,8 +735,8 @@
    // 测试系统打印查询语句
    if (options.sysType === 'local' && !window.GLOB.systemType) {
      param.custom_script &&  console.log(`${LText ? '' : '/*不执行默认sql*/\n'}${param.custom_script}`)
      LText &&  console.log(LText)
      param.custom_script &&  console.log(param.custom_script)
    }
    
    param.custom_script = Utils.formatOptions(param.custom_script)
src/tabviews/subtable/index.jsx
@@ -612,8 +612,8 @@
    // 测试系统打印查询语句
    if (options.sysType === 'local' && !window.GLOB.systemType) {
      param.custom_script &&  console.log(`${LText ? '' : '/*不执行默认sql*/\n'}${param.custom_script}`)
      LText &&  console.log(LText)
      param.custom_script &&  console.log(param.custom_script)
    }
    param.custom_script = Utils.formatOptions(param.custom_script)
src/tabviews/subtabtable/index.jsx
@@ -493,8 +493,8 @@
    
    // 测试系统打印查询语句
    if (options.sysType === 'local' && !window.GLOB.systemType) {
      param.custom_script &&  console.log(`${LText ? '' : '/*不执行默认sql*/\n'}${param.custom_script}`)
      LText &&  console.log(LText)
      param.custom_script &&  console.log(param.custom_script)
    }
    
    param.custom_script = Utils.formatOptions(param.custom_script)
src/tabviews/treepage/index.jsx
@@ -533,8 +533,8 @@
    
    // 测试系统打印查询语句
    if (options.sysType === 'local' && !window.GLOB.systemType) {
      param.custom_script &&  console.log(`${LText ? '' : '/*不执行默认sql*/\n'}${param.custom_script}`)
      LText &&  console.log(LText)
      param.custom_script &&  console.log(param.custom_script)
    }
    
    param.custom_script = Utils.formatOptions(param.custom_script)
src/tabviews/zshare/calendar/index.jsx
@@ -12,7 +12,8 @@
class Calendar extends Component {
  static propTpyes = {
    data: PropTypes.any,            // 事件数据
    levels: PropTypes.any
    calendar: PropTypes.any,
    loading: false
  }
  state = {
@@ -22,11 +23,11 @@
    selectYear: moment().format('YYYY'),
    selectMonth: moment().format('MM'),
    datelist: null,
    monthlist: null,
    monthlist: null
  }
  UNSAFE_componentWillMount() {
    const { levels } = this.props
    const { calendar, data } = this.props
    let yearlist = []
    let _selectYear = +this.state.selectYear
@@ -38,17 +39,14 @@
    }
    let datelist = this.getDateList(this.state.selectYear)
    let _levels = null
    if (!levels) {
      _levels = ['day', 'month', 'year']
    } else {
      _levels = levels
    if (data && data.length > 0) {
      datelist = this.mountdata(datelist, data)
    }
    let _levels = calendar.levels
    let level = _levels[0]
    let monthlist = null
    level = 'month'
    if (_levels.includes('month')) {
      monthlist = datelist.filter(item => item.month === moment().format('MM'))[0]
@@ -65,12 +63,112 @@
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!is(fromJS(this.props.data), fromJS(nextProps.data))) {
      let datelist = this.mountdata(this.state.datelist, nextProps.data)
      let monthlist = null
      if (this.state.levels.includes('month')) {
        monthlist = datelist.filter(item => item.month === this.state.selectMonth)[0]
      }
      this.setState({
        datelist: datelist,
        monthlist
      })
    } else if (!is(fromJS(this.props.calendar), fromJS(nextProps.calendar))) {
      this.setState({
        levels: nextProps.calendar.levels
      })
    }
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  mountdata = (datelist, data) => {
    const { calendar } = this.props
    let datalist = []
    let levels = {
      black: 1,
      red: 2,
      orange: 3,
      yellow: 4,
      green: 5,
      lightgreen: 6,
      cyan: 7,
      blue: 8,
      purple: 9,
      white: 10
    }
    data.forEach(item => {
      let startTime = item[calendar.startfield]
      let endTime = item[calendar.endfield]
      let color = item[calendar.colorfield]
      if (!startTime || !/^(1|2)\d{3}(-|\/)\d{2}(-|\/)\d{2}/.test(startTime)) return
      if (!endTime || !/^(1|2)\d{3}(-|\/)\d{2}(-|\/)\d{2}/.test(endTime)) return
      if (!item[calendar.remarkfield]) return
      datalist.push({
        color: color,
        level: color && levels[color] ? levels[color] : 100,
        remark: item[calendar.remarkfield],
        startMonth: startTime.substr(0, 4) + startTime.substr(5, 2),
        endMonth: endTime.substr(0, 4) + endTime.substr(5, 2),
        start: startTime.substr(0, 4) + startTime.substr(5, 2) + startTime.substr(8, 2),
        startTime: `${startTime.substr(5, 2)}-${startTime.substr(8, 2)}`,
        end: endTime.substr(0, 4) + endTime.substr(5, 2) + endTime.substr(8, 2),
        endTime: `${endTime.substr(5, 2)}-${endTime.substr(8, 2)}`
      })
    })
    if (datalist.length === 0) return datelist
    datalist.sort((a, b) => a.level - b.level)
    let styles = [
      {background: 'black', color: '#ffffff'},
      {background: 'red', color: '#ffffff'},
      {background: 'orange', color: '#ffffff'},
      {background: 'yellow', color: 'rgba(0,0,0,.65)'},
      {background: 'green', color: '#ffffff'},
      {background: 'lightgreen', color: 'rgba(0,0,0,.65)'},
      {background: 'cyan', color: 'rgba(0,0,0,.65)'},
      {background: 'blue', color: '#ffffff'},
      {background: 'purple', color: '#ffffff'},
      {background: 'white', color: 'rgba(0,0,0,.65)'},
    ]
    return datelist.map(month => {
      datalist.forEach(item => {
        if (item.startMonth <= month.time && item.endMonth >= month.time) {
          month.subData.push(item)
        }
      })
      month.sublist = month.sublist.map(week => {
        week.sublist = week.sublist.map(day => {
          if (!day) return null
          datalist.forEach(item => {
            if (item.start <= day.time && item.end >= day.time) {
              day.subData.push(item)
            }
          })
          if (day.subData[0]) {
            day.style = styles[day.subData[0].level - 1] || null
          }
          return day
        })
        return week
      })
      return month
    })
  }
  getDateList = (selectYear) => {
@@ -91,10 +189,12 @@
      }
      for (let i = 1; i <= end; i++) {
        let _day = i < 10 ? `0${i}` : `${i}`
        if (_weeklist[_weeklist.length - 1].sublist.length < 7) {
          _weeklist[_weeklist.length - 1].sublist.push({day: i < 10 ? `0${i}` : `${i}`, label: i})
          _weeklist[_weeklist.length - 1].sublist.push({day: _day, time: selectYear + month + _day, label: i, subData: []})
        } else {
          let _week = {week: _weeklist.length + 1, sublist: [{day: i < 10 ? `0${i}` : `${i}`, label: i}]}
          let _week = {week: _weeklist.length + 1, sublist: [{day: _day, time: selectYear + month + _day, label: i, subData: []}]}
          _weeklist.push(_week)
        }
      }
@@ -106,8 +206,10 @@
      datelist.push({
        month: month,
        time: selectYear + month,
        label: monthName[month],
        sublist: _weeklist
        sublist: _weeklist,
        subData: []
      })
    })
@@ -128,6 +230,10 @@
    }
    this.setState({ selectYear: value, datelist, monthlist })
    if (this.props.changeDate) {
      this.props.changeDate(value)
    }
  }
  monthChange = (value) => {
@@ -144,14 +250,10 @@
  }
  render() {
    const { loading } = this.props
    const { level, selectMonth, selectYear, yearlist, levels, datelist, monthlist } = this.state
    const _levelName = {day: '日', month: '月', year: '年'}
    const listData = [
      { type: 'orange', content: 'This is error event 2.' },
      { type: 'cyan', content: 'This is error event 3.' },
      { type: 'green', content: 'This is error event 4.' },
    ]
    console.log(loading)
    return (
      <div className="mk-calendar">
        <div className="mk-calendar-control">
@@ -194,12 +296,17 @@
                      <tr key={cell.week}>
                        {cell.sublist.map((d, i) => (
                          <td key={i}>
                            {d ? <div className={'day-wrap ' + d.class} onClick={() => this.triggerDay(d)}>
                              {d.label ? <Popover mouseEnterDelay={0.3} overlayClassName="calendar-day-pop" content={
                            {d ? <div className={'day-wrap ' + d.class} style={d.style || null} onClick={() => this.triggerDay(d)}>
                              {d.subData.length > 0 ? <Popover mouseEnterDelay={0.3} overlayClassName="calendar-day-pop" content={
                                <div>
                                  {listData.map((data, index) => (
                                  {d.subData.map((data, index) => (
                                    <div key={index} className="message">
                                      <Badge color={data.type} text={data.content} />
                                      <Badge color={data.color} text={
                                        <span>
                                          {data.remark}
                                          <span style={{color: 'rgba(0,0,0,.45)'}}>({data.startTime + ' ~ ' + data.endTime})</span>
                                        </span>}
                                      />
                                    </div>
                                  ))}
                                </div>
@@ -228,14 +335,14 @@
                  <tr key={cell.week}>
                    {cell.sublist.map((d, i) => (
                      <td key={i}>
                        {d ? <div className="month-wrap" onClick={() => this.triggerDay(d)}>
                        {d ? <div className="month-wrap" style={d.style || null} onClick={() => this.triggerDay(d)}>
                          <div className="header">
                            {d.label}
                          </div>
                          <ul className="content">
                            {[1,3,7,18,22].includes(d.label) && listData.map((data, index) => (
                              <li key={index} className="message" title={data.content}>
                                <Badge color={data.type} text={data.content} />
                            {d.subData.map((data, index) => (
                              <li key={index} className="message" title={data.remark}>
                                <Badge color={data.color} text={data.remark} />
                              </li>
                            ))}
                          </ul>
@@ -255,9 +362,9 @@
                    {item.label}
                  </div>
                  <ul className="content">
                    {['01', '05', '10'].includes(item.month) && listData.map((data, index) => (
                      <li key={index} className="message" title={data.content}>
                        <Badge color={data.type} text={data.content} />
                    {item.subData.map((data, index) => (
                      <li key={index} className="message" title={data.remark}>
                        <Badge color={data.color} text={data.remark} />
                      </li>
                    ))}
                  </ul>
src/tabviews/zshare/calendar/index.scss
@@ -1,6 +1,6 @@
.mk-calendar {
  width: 100%;
  padding: 10px;
  padding: 20px;
  .mk-calendar-control {
    text-align: right;
@@ -84,9 +84,12 @@
                  right: 0;
                  .message {
                    width: 100%;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    // white-space: nowrap;
                    // overflow: hidden;
                    // text-overflow: ellipsis;
                    .ant-badge-status-text {
                      color: inherit;
                    }
                  } 
                }
                .content::-webkit-scrollbar {
@@ -157,12 +160,12 @@
  .message {
    .ant-badge-status-text {
      display: inline-block;
      min-width: 100px;
      max-width: 200px;
      white-space: nowrap;
      min-width: 200px;
      max-width: 350px;
      // white-space: nowrap;
      vertical-align: middle;
      overflow: hidden;
      text-overflow: ellipsis;
      // overflow: hidden;
      // text-overflow: ellipsis;
    }
  }
  .ant-popover-inner-content {
src/tabviews/zshare/mutilform/index.jsx
@@ -5,7 +5,7 @@
import moment from 'moment'
import Api from '@/api'
import { formRule } from '@/utils/option.js'
import { formRule, calendarColors } from '@/utils/option.js'
import Utils from '@/utils/utils.js'
import FileUpload from '../fileupload'
import './index.scss'
@@ -564,6 +564,28 @@
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'color') { // 颜色选择
        fields.push(
          <Col span={24 / cols} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.field, {
                initialValue: item.initval,
                rules: [
                  {
                    required: item.required === 'true',
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select onChange={(value, option) => {this.selectChange(item, value, option)}} disabled={item.readonly === 'true'}>
                  {calendarColors.map(option =>
                    <Select.Option key={option.name} style={{background: option.name, color: option.value}} value={option.name}>{option.name}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'select' || item.type === 'link') { // 下拉搜索
        let hasSubField = false
        if (item.linkSubField && item.linkSubField.length > 0) { // 存在关联字段,数据存储
src/templates/calendarconfig/calcomponent/calendarform/index.jsx
New file
@@ -0,0 +1,139 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Select, Checkbox } from 'antd'
import './index.scss'
class MainTab extends Component {
  static propTpyes = {
    config: PropTypes.object,    // 页面配置
    dict: PropTypes.object,      // 字典项
    calendar: PropTypes.any      // 日历配置信息
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const { getFieldDecorator } = this.props.form
    const { calendar, config } = this.props
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 10 }
      }
    }
    return (
      <Form {...formItemLayout} className="model-calendar-form">
        <Row gutter={24}>
          <Col span={24}>
            <Form.Item label={'开始时间'}>
              {getFieldDecorator('startfield', {
                initialValue: calendar.startfield,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + '开始时间!'
                  }
                ]
              })(
                <Select>
                  {config.columns.map(option =>
                    <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label={'结束时间'}>
              {getFieldDecorator('endfield', {
                initialValue: calendar.endfield,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + '结束时间!'
                  }
                ]
              })(
                <Select>
                  {config.columns.map(option =>
                    <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label={'信息字段'}>
              {getFieldDecorator('remarkfield', {
                initialValue: calendar.remarkfield,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + '信息字段!'
                  }
                ]
              })(
                <Select>
                  {config.columns.map(option =>
                    <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label={'颜色字段'}>
              {getFieldDecorator('colorfield', {
                initialValue: calendar.colorfield,
                rules: [
                  {
                    required: false,
                    message: this.props.dict['form.required.select'] + '颜色字段!'
                  }
                ]
              })(
                <Select>
                  {config.columns.map(option =>
                    <Select.Option key={option.uuid} value={option.field}>{option.label}</Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={24}>
            <Form.Item label={'日历等级'}>
              {getFieldDecorator('levels', {
                initialValue: calendar.levels,
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + '日历等级!'
                  }
                ]
              })(
                <Checkbox.Group options={[{ value: 'day', label: '日' }, { value: 'month', label: '月' }, { value: 'year', label: '年' }]}/>
              )}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(MainTab)
src/templates/calendarconfig/calcomponent/calendarform/index.scss
New file
@@ -0,0 +1,7 @@
.model-calendar-form {
  min-height: 180px;
  .anticon-question-circle {
    color: #c49f47;
    margin-right: 3px;
  }
}
src/templates/calendarconfig/calcomponent/index.jsx
New file
@@ -0,0 +1,98 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import CalendarForm from './calendarform'
import './index.scss'
class SettingComponent extends Component {
  static propTpyes = {
    config: PropTypes.any,         // 标签
    updateConfig: PropTypes.func,
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    calendar: null,  // 日历设置
    visible: false,  // 模态框控制
  }
  /**
   * @description 保存页面配置信息
   */
  calendarSave = () => {
    const { config } = this.props
    this.calendarRef.handleConfirm().then(res => {
      this.setState({
        visible: false
      })
      this.props.updateConfig({...config, calendar: res})
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  /**
   * @description 添加或修改标签
   */
  handleTab = (e) => {
    const { config } = this.props
    e.stopPropagation()
    let calendar = fromJS(config.calendar).toJS()
    this.setState({
      visible: true,
      calendar
    })
  }
  render() {
    const { config } = this.props
    const { dict, visible, calendar } = this.state
    return (
      <div className="model-calendar-setting">
        <Icon type="edit" onClick={this.handleTab} />
        {/* 设置全局配置及列表数据源 */}
        <Modal
          wrapClassName="model-calendar-setting-modal"
          title={dict['model.edit']}
          visible={visible}
          width={700}
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false })}}
          cancelText={this.state.dict['model.cancel']}
          okText={this.state.dict['model.confirm']}
          onOk={this.calendarSave}
          destroyOnClose
        >
          <CalendarForm
            dict={dict}
            config={config}
            calendar={calendar}
            wrappedComponentRef={(inst) => this.calendarRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default SettingComponent
src/templates/calendarconfig/calcomponent/index.scss
New file
@@ -0,0 +1,40 @@
.model-calendar-setting {
  position: absolute;
  right: 0;
  top: 0;
  >.anticon-edit {
    font-size: 18px;
    padding: 5px;
    margin-right: 10px;
    color: #1890ff;
    cursor: pointer;
  }
}
.model-calendar-setting-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;
    }
  }
}
src/templates/calendarconfig/index.jsx
New file
@@ -0,0 +1,890 @@
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 } from 'antd'
import moment from 'moment'
import Api from '@/api'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import Utils from '@/utils/utils.js'
import { getMainMenuForm } from '@/templates/zshare/formconfig'
import asyncComponent from '@/utils/asyncComponent'
import SearchComponent from '@/templates/sharecomponent/searchcomponent'
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 SettingComponent = asyncComponent(() => import('@/templates/sharecomponent/datasourcecomponent'))
const TabComponent = asyncComponent(() => import('./tabcomponent'))
const CalComponent = asyncComponent(() => import('./calcomponent'))
const TableComponent = asyncComponent(() => import('@/templates/sharecomponent/tablecomponent'))
const FieldsComponent = asyncComponent(() => import('@/templates/sharecomponent/fieldscomponent'))
const CalendarComponent = asyncComponent(() => import('@/tabviews/zshare/calendar'))
class SubTableConfig 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,            // 页面配置
    visible: false,          // 搜索条件、按钮、显示列,模态框显示控制
    tableFields: [],         // 已选表字段集
    fields: null,            // 搜索条件及显示列,可选字段
    menuformlist: null,      // 基本信息表单字段
    formlist: null,          // 搜索条件、按钮、显示列表单字段
    menuloading: false,      // 菜单保存中
    menucloseloading: false, // 菜单关闭时,选择保存
    loading: false,          // 加载中,页面spin
    closeVisible: false,     // 关闭模态框
    originConfig: null,      // 原配置
    tabviews: [],            // 所有标签页
    optionLibs: null,        // 自定义下拉选项库
    activeKey: '0',          // 默认展开基本信息
    pasteContent: null,      // 粘贴内容
    openEdition: '',         // 编辑版本标记,防止多人操作
    mockdata: [],            // 测试数据
    mockloading: false       // 数据加载中
  }
  /**
   * @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.search.forEach(item => {
        if (
          (item.type === 'select' || item.type === 'multiselect' || item.type === 'link') &&
          item.resourceType === '0' &&
          item.options && item.options.length > 0
        ) {
          optionLibs.set(menu.MenuID + item.uuid, {
            uuid: menu.MenuID + item.uuid,
            label: item.label,
            parname: menu.MenuName,
            type: 'search',
            options: item.options
          })
        }
      })
    }
    this.setState({
      openEdition: menu.open_edition || '',
      optionLibs: optionLibs,
      activeKey: menu.activeKey || '0',
      config: _config,
      originMenu: fromJS(menu).toJS(),
      menuformlist: getMainMenuForm(menu, _config),
      mockdata: this.getMockData(moment().format('YYYY'))
    })
  }
  /**
   * @description 加载完成后
   * 1、获取系统可使用表
   * 2、根据配置信息中已使用表获取相关字段信息
   */
  componentDidMount () {
    this.reloadTab(false)
  }
  getMockData = (year) => {
    let msgs = [
      {color: 'red', remark: '服务器异常,请联系运维人员!'},
      {color: 'orange', remark: '系统异常,请及时处理!'},
      {color: 'yellow', remark: '您的订单异常,请联系客服!'},
      {color: 'green', remark: '您的订单已完成。'},
      {color: 'lightgreen', remark: '消息已发送,请及时查收。'},
      {color: 'cyan', remark: '您有一条新的消息。'},
      {color: 'blue', remark: '任务未完成,请注意后续工作。'},
      {color: 'purple', remark: '您有新的任务等待处理!'}
    ]
    let mockdata = []
    for (let i = 1; i <= 12; i++) {
      if (Math.random() > 0.5) {
        let cell = {uuid: Utils.getuuid()}
        let msg = msgs[Math.floor(Math.random() * 8)]
        let day = Math.floor(Math.random() * 28 + 1)
        cell.color = msg.color
        cell.remark = msg.remark
        cell.start = `${year}-${i < 10 ? '0' + i : i}-${day < 10 ? '0' + day : day}`
        cell.end = moment(cell.start, 'YYYY-MM-DD').add(Math.floor(Math.random() * 10), 'days').format('YYYY-MM-DD')
        mockdata.push(cell)
        if (Math.random() > 0.5) {
          let _cell = {uuid: Utils.getuuid()}
          let _msg = msgs[Math.floor(Math.random() * 8)]
          let _day = Math.floor(Math.random() * 28 + 1)
          _cell.color = _msg.color
          _cell.remark = _msg.remark
          _cell.start = `${year}-${i < 10 ? '0' + i : i}-${_day < 10 ? '0' + _day : _day}`
          _cell.end = moment(_cell.start, 'YYYY-MM-DD').add(Math.floor(Math.random() * 10), 'days').format('YYYY-MM-DD')
          mockdata.push(_cell)
        }
      }
    }
    return mockdata
  }
  /**
   * @description 加载或刷新标签信息
   */
  reloadTab = (type) => {
    this.setState({
      loading: type,
      tabviews: []
    })
    Api.getSystemConfig({func: 'sPC_Get_UserTemp', TypeCharTwo: 'tab'}).then(res => {
      if (res.status) {
        let _tabviews = []
        res.UserTemp.forEach(temp => {
          let item = {
            uuid: temp.MenuID,
            value: temp.MenuID,
            text: temp.MenuName,
            type: temp.Template,
            MenuNo: temp.MenuNo
          }
          _tabviews.push(item)
        })
        this.setState({
          loading: false,
          tabviews: _tabviews
        })
        if (type) {
          notification.success({
            top: 92,
            message: '刷新成功。',
            duration: 2
          })
        }
      } else {
        this.setState({
          loading: false
        })
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  // 页面返回
  handleViewBack = () => {
    let param = {
      editMenu: null,
      optionLibs: null,
      editTab: null,
      tabConfig: null,
      subTabConfig: null,
      btnTab: null,
      btnTabConfig: null,
      editAction: null,
      subConfig: null,
      tabview: ''
    }
    this.props.handleView(param)
  }
  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, openEdition } = this.state
    let config = fromJS(this.state.config).toJS()
    this.menuformRef.handleConfirm().then(res => {
      if (config.isAdd) {
        config.search = config.search.filter(item => !item.origin)
      }
      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 || ''
      })
      if (_config.tab) {
        _config.funcs.push({
          type: 'tab',
          subtype: 'tab',
          uuid: _config.tab.uuid,
          label: _config.tab.label,
          linkTab: _config.tab.linkTab
        })
      }
      if (this.state.closeVisible) { // 显示关闭对话框时,模态框中保存按钮,显示保存中状态
        this.setState({
          menucloseloading: true
        })
      } else {
        this.setState({
          menuloading: true
        })
      }
      new Promise(resolve => {
        if (_config.tab) {
          Api.getSystemConfig({
            func: 'sPC_Get_LongParam',
            MenuID: _config.tab.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) {
                _config.funcs[1].menuNo = _LongParam.tabNo || ''
                _config.funcs[1].subfuncs = _LongParam.funcs || []
              }
            }
            resolve()
          })
        } else {
          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 tabParam = { // 添加菜单tab页
          func: 'sPC_sMenusTab_AddUpt',
          MenuID: menu.MenuID
        }
        if (_config.tab) {
          tabParam.LText = Utils.formatOptions(`select '${menu.MenuID}' as MenuID ,'${_config.tab.linkTab}' as Tabid,'${_config.tab.label}' as TabName ,'0' as Sort`)
        } else {
          tabParam.LText = Utils.formatOptions(`select '${menu.MenuID}' as MenuID ,'' as Tabid,'' as TabName ,'0' as Sort`)
        }
        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
        }
        // 保存本地
        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()
            // 标签信息保存
            Api.getSystemConfig(tabParam).then(result => {
              if (result.status) {
                notification.success({
                  top: 92,
                  message: '保存成功',
                  duration: 2
                })
                if (this.state.closeVisible) {
                  this.handleViewBack()
                } else {
                  this.setState({
                    menuloading: false,
                    menucloseloading: false
                  })
                }
              } else {
                notification.warning({
                  top: 92,
                  message: result.message,
                  duration: 5
                })
                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['model.menu.basemsg'],
        duration: 5
      })
    })
  }
  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['model.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 = () => {
    const { menu } = this.props
    const { config, originMenu, optionLibs, activeKey, openEdition } = this.state
    if (config.isAdd) { // 新建菜单,提示菜单尚未保存
      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: fromJS(config.tab).toJS(),
          tabConfig: null,
          editSubTab: null,
          subTabConfig: null,
          btnTab: null,
          btnTabConfig: null,
          editAction: '',
          subConfig: '',
          tabview: config.tab.type
        }
        // 当子表使用主页搜索条件时,将主页搜索向下传递
        if (param.editTab && param.editTab.searchPass === 'true') {
          param.editTab.mainsearch = fromJS(_config.search).toJS()
        }
        this.setState({
          loading: true
        })
        Api.getSystemConfig({
          func: 'sPC_Get_LongParam',
          MenuID: config.tab.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 hasKey = false
    let chartcols = []
    config.columns.forEach(col => {
      if (col.field) {
        chartcols.push(col.field)
      }
      if (config.setting.primaryKey === col.field) {
        hasKey = true
      }
    })
    if (config.setting.interType === 'inner' && !config.setting.innerFunc && config.setting.default !== 'false' && !config.setting.dataresource) {
      return '菜单尚未设置数据源,不可启用!'
    } else if (!config.setting.primaryKey) {
      return '菜单尚未设置主键,不可启用!'
    } else if (!hasKey) {
      return '显示列中不存在主键字段,不可启用!'
    } else {
      return true
    }
  }
  /**
   * @description 编辑功能完成更新,包括解冻按钮、粘贴、替换等
   */
  updateConfig = (res) => {
    if (res.type === 'thaw') {
      this.setState({
        config: res.config
      })
    } else if (res.type === 'paste') {
      this.setState({
        pasteContent: res.content
      }, () => {
        this.setState({
          pasteContent: null
        })
      })
    }
  }
  /**
   * @description 更新搜索条件配置信息
   */
  updatesearch = (config, options) => {
    const { optionLibs } = this.state
    this.setState({
      config: config,
      optionLibs: options || optionLibs
    })
  }
  /**
   * @description 更新显示列配置信息
   */
  updateconfig = (config) => {
    this.setState({
      config: config
    })
  }
  /**
   * @description 更新常用表信息,快捷添加后更新配置信息
   */
  updatetable = (config, fields) => {
    const { tableFields } = this.state
    this.setState({
      config: config,
      tableFields: fields ? fields : tableFields
    })
  }
  /**
   * @description 批量添加,更新配置信息
   */
  updatefield = (config) => {
    this.setState({
      config: config
    })
  }
  // 年切换时重新生成数据
  changeDate = (year) => {
    this.setState({mockloading: true}, () => {
      this.setState({
        mockloading: false,
        mockdata: this.getMockData(year)
      })
    })
  }
  render () {
    const { activeKey, config, tabviews, mockdata, mockloading } = this.state
    return (
      <div className="model-subtable-board">
        <DndProvider backend={HTML5Backend}>
          {/* 工具栏 */}
          <div className="tools">
            <Collapse accordion defaultActiveKey={activeKey} bordered={false} onChange={(key) => this.setState({activeKey: key})}>
              {/* 基本信息 */}
              <Panel forceRender={true} header={'标签基本信息'} key="0" id="subtable-basedata">
                {/* 菜单信息 */}
                <MenuForm
                  dict={this.state.dict}
                  formlist={this.state.menuformlist}
                  wrappedComponentRef={(inst) => this.menuformRef = inst}
                />
                {/* 表名添加 */}
                <TableComponent
                  config={config}
                  containerId="subtable-basedata"
                  updatetable={this.updatetable}
                />
              </Panel>
              {/* 搜索条件添加 */}
              <Panel header={this.state.dict['header.menu.search']} key="1">
                <div className="search-element">
                  {Source.searchItems.map((item, index) => {
                    return (<SourceElement key={index} content={item}/>)
                  })}
                </div>
                <FieldsComponent
                  config={config}
                  type="search"
                  tableFields={this.state.tableFields}
                  updatefield={this.updatefield}
                />
              </Panel>
            </Collapse>
          </div>
          <div className="setting">
            <Card title={
              <div>
                日历页面配置
                <Icon type="redo" style={{marginLeft: '10px'}} title="刷新标签列表" onClick={() => this.reloadTab(true)} />
              </div>
            } bordered={false} extra={
              <div>
                {/* <EditComponent dict={this.state.dict} type="subtable" config={this.state.config} refresh={this.updateConfig}/> */}
                <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['model.save']}</Button>
                <Button onClick={this.cancelConfig}>{this.state.dict['model.back']}</Button>
              </div>
            } style={{ width: '100%' }}>
              <SettingComponent
                type="subtable"
                config={config}
                MenuID={config.uuid}
                menuformRef={this.menuformRef}
                permFuncField={this.props.permFuncField}
                updateConfig={this.updateconfig}
              />
              <SearchComponent
                menu={{MenuID: config.uuid, MenuName: config.tabName}}
                config={config}
                pasteContent={this.state.pasteContent}
                sysRoles={this.props.sysRoles}
                optionLibs={this.state.optionLibs}
                updatesearch={this.updatesearch}
              />
              <div className="calendar-wrap">
                <TabComponent config={config} updateConfig={this.updateconfig} tabviews={tabviews} setSubConfig={this.setSubConfig} />
                <CalComponent config={config} updateConfig={this.updateconfig} />
                <CalendarComponent calendar={{
                  levels: config.calendar.levels, startfield: 'start', endfield: 'end', colorfield: 'color', remarkfield: 'remark'
                }} loading={mockloading} data={mockdata} changeDate={this.changeDate}/>
              </div>
            </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['model.save']}</Button>,
            <Button key="confirm" className="mk-btn mk-yellow" onClick={this.handleViewBack}>{this.state.dict['model.notsave']}</Button>,
            <Button key="cancel" onClick={() => { this.setState({closeVisible: false}) }}>{this.state.dict['model.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)(SubTableConfig)
src/templates/calendarconfig/index.scss
New file
@@ -0,0 +1,215 @@
.model-subtable-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;
        }
      }
    }
    .config-btn {
      position: relative;
      .config-btn-title {
        margin-top: 20px;
        margin-bottom: 10px;
        color: #1890ff;
        border-bottom: 1px solid #e8e8e8;
      }
    }
    .ant-list {
      margin-top: 20px;
      .ant-list-item {
        display: -webkit-box;
        padding-right: 20px;
        position: relative;
        padding-left: 5px;
        overflow: hidden;
        text-overflow: ellipsis;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        min-height: 55px;
        width: 100%;
        .anticon {
          position: absolute;
          top: 0px;
          right: 0px;
          padding: 3px 3px 10px 10px;
          cursor: pointer;
        }
        .bottom-mask {
          position: absolute;
          width: 100%;
          height: 8px;
          bottom: 0;
          left: 0;
          background: #ffffff;
          border-radius: 8px;
        }
      }
    }
    .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: 0;
      .chart-view {
        margin-bottom: 70px;
        .chart-title {
          position: relative;
          color: rgba(0, 0, 0, 0.65);
          font-weight: 400;
          font-size: 16px;
          text-overflow: ellipsis;
          white-space: nowrap;
          overflow: hidden;
          margin: 0 20px;
          padding: 10px 5px 5px;
        }
      }
      > .anticon-setting {
        position: absolute;
        font-size: 18px;
        right: 7px;
        top: 10px;
      }
      .calendar-wrap {
        position: relative;
        padding-top: 10px;
      }
    }
  }
  .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/calendarconfig/source.jsx
New file
@@ -0,0 +1,139 @@
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 CalendarBaseData {
  baseConfig = {
    version: '1.0',
    type: 'system',
    Template: 'CalendarPage',
    enabled: false,
    tabName: '',
    tabNo: '',
    Remark: '',
    uuid: Utils.getuuid(),
    setting: {
      tableName: '',
      primaryKey: '',
      dataresource: '',
      interType: 'inner',
      innerFunc: '',
      interface: '',
      outerFunc: ''
    },
    columns: [],
    scripts: [],
    tables: [],
    tab: null,
    calendar: {
      levels: ['day', 'month', 'year'],
      startfield: '',
      endfield: '',
      colorfield: '',
      remarkfield: ''
    },
    search: [
      {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'label',
        field: '',
        initval: '',
        type: 'text',
        resourceType: '0',
        setAll: 'false',
        options: [],
        orderType: 'asc',
        match: 'like',
        display: 'dropdown'
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'label',
        field: '',
        initval: '',
        type: 'select',
        resourceType: '0',
        setAll: 'false',
        options: [],
        orderType: 'asc',
        match: 'equal',
        display: 'dropdown'
      }, {
        origin: true,
        uuid: Utils.getuuid(),
        label: 'label',
        field: '',
        initval: '',
        type: 'date',
        resourceType: '0',
        setAll: 'false',
        options: [],
        orderType: 'asc',
        match: 'greater',
        display: 'dropdown'
      }
    ]
  }
  searchItems = [
    {
      type: 'search',
      label: CommonDict['model.form.text'],
      subType: 'text',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.select'],
      subType: 'select',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.multiselect'],
      subType: 'multiselect',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.link'],
      subType: 'link',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.dateday'],
      subType: 'date',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.dateweek'],
      subType: 'dateweek',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.datemonth'],
      subType: 'datemonth',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.daterange'],
      subType: 'daterange',
      url: ''
    },
    {
      type: 'search',
      label: CommonDict['model.form.dategroup'],
      subType: 'group',
      url: ''
    }
  ]
}
export default new CalendarBaseData()
src/templates/calendarconfig/tabcomponent/index.jsx
New file
@@ -0,0 +1,122 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import { getTabForm } from '@/templates/zshare/formconfig'
import TabForm from './tabform'
import './index.scss'
const { confirm } = Modal
class SettingComponent extends Component {
  static propTpyes = {
    config: PropTypes.any,         // 标签
    tabviews: PropTypes.array,     // 标签集
    updateConfig: PropTypes.func,
    setSubConfig: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    menu: null,          // 菜单信息
    formlist: null,      // 表单信息
    visible: false       // 模态框控制
  }
  /**
   * @description 保存页面配置信息
   */
  tabSave = () => {
    const { config } = this.props
    this.tabRef.handleConfirm().then(res => {
      this.setState({
        visible: false
      })
      this.props.updateConfig({...config, tab: res})
    })
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState)) || !is(fromJS(this.props.config.tab), fromJS(nextProps.config.tab))
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  /**
   * @description 添加或修改标签
   */
  handleTab = (e) => {
    e.stopPropagation()
    const { config } = this.props
    this.setState({
      visible: true,
      formlist: getTabForm(config.tab || {}, '', [], '', [], config.Template)
    })
  }
  closeTab = (e) => {
    const { config } = this.props
    const _this = this
    e.stopPropagation()
    confirm({
      content: '确定删除标签吗?',
      okText: this.state.dict['model.confirm'],
      cancelText: this.state.dict['model.cancel'],
      onOk() {
        _this.props.updateConfig({...config, tab: ''})
      },
      onCancel() {}
    })
  }
  render() {
    const { tabviews, config } = this.props
    const { dict, visible } = this.state
    return (
      <div className="model-calendar-tab">
        {config.tab ? <div className="tab-control">
          <span onDoubleClick={this.props.setSubConfig}>{config.tab.label}</span>
          <Icon type="edit" onClick={this.handleTab} />
          <Icon type="close" onClick={this.closeTab} />
        </div> : <Icon title="添加标签" type="plus" onClick={this.handleTab} />}
        {/* 设置全局配置及列表数据源 */}
        <Modal
          wrapClassName="model-calendar-tab-modal"
          title={dict['model.edit']}
          visible={visible}
          width={900}
          maskClosable={false}
          onCancel={() => { this.setState({ visible: false })}}
          cancelText={this.state.dict['model.cancel']}
          okText={this.state.dict['model.confirm']}
          onOk={this.tabSave}
          destroyOnClose
        >
          <TabForm
            tabs={tabviews}
            dict={dict}
            inputSubmit={this.tabSave}
            formlist={this.state.formlist}
            wrappedComponentRef={(inst) => this.tabRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default SettingComponent
src/templates/calendarconfig/tabcomponent/index.scss
New file
@@ -0,0 +1,85 @@
.model-calendar-tab {
  position: absolute;
  top: 0;
  >.anticon-plus {
    position: absolute;
    font-size: 18px;
    left: 10px;
    top: 10px;
    color: #26c281;
    cursor: pointer;
  }
  .tab-control {
    position: absolute;
    padding: 15px 0px 0px 10px;
    left: 0px;
    top: 0px;
    span {
      display: inline-block;
      white-space: nowrap;
      font-size: 16px;
      cursor: pointer;
      padding: 0 10px;
      border-bottom: 2px solid #1890ff;
    }
    .anticon-edit {
      position: absolute;
      font-size: 14px;
      left: 10px;
      top: 2px;
      color: #1890ff;
      cursor: pointer;
      display: none;
    }
    .anticon-close {
      position: absolute;
      font-size: 14px;
      left: 35px;
      top: 2px;
      color: #ff4d4f;
      cursor: pointer;
      display: none;
    }
  }
  .tab-control:hover {
    .anticon-edit {
      display: inline-block;
    }
    .anticon-close {
      display: inline-block;
    }
  }
}
.model-calendar-tab-modal {
  .ant-modal {
    top: 50px;
    padding-bottom: 5px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
      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);
    }
    .ant-empty-normal {
      margin: 5px 0px;
    }
  }
}
src/templates/calendarconfig/tabcomponent/tabform/index.jsx
New file
@@ -0,0 +1,278 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
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'
class MainTab extends Component {
  static propTpyes = {
    tabs: PropTypes.array,       // 可关联标签集
    dict: PropTypes.object,      // 字典项
    formlist: PropTypes.any,     // 表单
    inputSubmit: PropTypes.any   // 回车提交事件
  }
  state = {
    formlist: null // 表单
  }
  /**
   * @description 表单预处理
   */
  UNSAFE_componentWillMount () {
    const { formlist } = this.props
    let type = formlist.filter(cell => cell.key === 'type')[0].initVal
    let _tabs = this.props.tabs.filter(tab => tab.type === type)
    this.setState({
      formlist: formlist.map(item => {
        if (item.key === 'linkTab') {
          item.options = [
            {
              value: '',
              text: '新建'
            },
            ..._tabs
          ]
        }
        return item
      })
    })
  }
  /**
   * @description 标签页类型切换
   */
  openTypeChange = (key, value) => {
    const { formlist } = this.state
    if (key === 'type') {
      let _tabs = this.props.tabs.filter(tab => tab.type === value)
      this.setState({
        formlist: formlist.map(item => {
          if (item.key === 'linkTab') {
            item.options = [
              {
                value: '',
                text: '新建'
              },
              ..._tabs
            ]
            item.initVal = ''
          }
          return item
        })
      }, () => {
        if (this.props.form.getFieldValue('linkTab') !== undefined) {
          this.props.form.setFieldsValue({linkTab: ''})
        }
      })
    }
  }
  handleSubmit = (e) => {
    e.preventDefault()
    if (this.props.inputSubmit) {
      this.props.inputSubmit()
    }
  }
  getFields() {
    const { getFieldDecorator } = this.props.form
    const fields = []
    this.state.formlist.forEach((item, index) => {
      if (item.hidden || item.forbid) return
      if (item.type === 'text') {
        let rules = []
        if (item.key === 'foreignKey') {
          rules.push({
            pattern: /^[a-zA-Z_]*$/ig,
            message: item.label + '字段只允许包含字母及下划线!'
          })
        }
        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 + '!'
                  },
                  {
                    max: formRule.input.max,
                    message: formRule.input.message
                  },
                  ...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>
        )
      } else if (item.type === 'select') { // 下拉搜索
        fields.push(
          <Col span={12} key={index}>
            <Form.Item label={item.label}>
              {getFieldDecorator(item.key, {
                initialValue: item.initVal,
                rules: [
                  {
                    required: !!item.required,
                    message: this.props.dict['form.required.select'] + item.label + '!'
                  }
                ]
              })(
                <Select
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={(value) => {this.openTypeChange(item.key, value)}}
                >
                  {item.options.map((option, i) =>
                    <Select.Option id={'mk' + i} title={option.text} key={'mk' + i} value={option.value}>
                      {item.key === 'icon' && i !== 0 ? <Icon type={option.text} /> : option.text}
                    </Select.Option>
                  )}
                </Select>
              )}
            </Form.Item>
          </Col>
        )
      } else if (item.type === 'mutilselect') {
        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
              })(
                <Select
                  mode="multiple"
                  style={{ width: '100%' }}
                  placeholder=""
                >
                  {item.options.map((option, index) =>
                    <Select.Option id={option.uuid} title={option.label} key={index} value={option.uuid}>{option.label}</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>
                  {
                    item.options.map(option => {
                      return (
                        <Radio key={option.value} value={option.value}>{option.text}</Radio>
                      )
                    })
                  }
                </Radio.Group>
              )}
            </Form.Item>
          </Col>
        )
      }
    })
    return fields
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          if (!values.linkTab) { // 没有关联标签(新建时),创建新标签Id
            values.linkTab = Utils.getuuid()
          }
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  render() {
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="model-tab-form">
        <Row gutter={24}>{this.getFields()}</Row>
      </Form>
    )
  }
}
export default Form.create()(MainTab)
src/templates/calendarconfig/tabcomponent/tabform/index.scss
New file
@@ -0,0 +1,16 @@
.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/comtableconfig/index.jsx
@@ -541,6 +541,11 @@
        _LText = _LText.join(' union all ')
        // 清空菜单下关联的标签
        if (!_LText) {
          _LText = `select '${menu.MenuID}' as MenuID ,'' as Tabid,'' as TabName ,'0' as Sort`
        }
        tabParam.LText = Utils.formatOptions(_LText)
        tabParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss') + '.000'
        tabParam.secretkey = Utils.encrypt(tabParam.LText, tabParam.timestamp)
@@ -751,14 +756,12 @@
    new Promise(resolve => {
      let deffers = []
      if (tabParam.LText) {
        let defer = new Promise(resolve => {
          Api.getSystemConfig(tabParam).then(result => {
            resolve(result)
          })
      let defer = new Promise(resolve => {
        Api.getSystemConfig(tabParam).then(result => {
          resolve(result)
        })
        deffers.push(defer)
      }
      })
      deffers.push(defer)
      if (btnParam.LText) {
        let defer = new Promise(resolve => {
@@ -978,7 +981,7 @@
    const { menu } = this.props
    const { config, originMenu, optionLibs, activeKey, openEdition } = this.state
    if (!originMenu.MenuID) { // menuID不存在时,为新建菜单,提示菜单尚未保存
    if (config.isAdd) { // 新建菜单,提示菜单尚未保存
      notification.warning({
        top: 92,
        message: this.state.dict['header.menu.config.notsave'],
@@ -1281,12 +1284,10 @@
  /**
   * @description 更新标签配置信息
   */
  updatetabs = (config, delcards) => {
    const { delActions } = this.state
  updatetabs = (config) => {
    this.setState({
      config: config,
      delActions: delcards ? [...delActions, ...delcards] : delActions
      config: config
    })
  }
src/templates/menuconfig/editthdmenu/index.jsx
@@ -29,6 +29,7 @@
const ComTableConfig = asyncLoadComponent(() => import('@/templates/comtableconfig'))
const TreePageConfig = asyncLoadComponent(() => import('@/templates/treepageconfig'))
const CalendarPageConfig = asyncLoadComponent(() => import('@/templates/calendarconfig'))
const FormTabConfig = asyncLoadComponent(() => import('@/templates/formtabconfig'))
const ModalConfig = asyncLoadComponent(() => import('@/templates/modalconfig'))
const SubTable = asyncLoadComponent(() => import('@/templates/subtableconfig'))
@@ -39,7 +40,8 @@
const illust = { // 模板图片,用于已使用模板
  CommonTable: mainsubtable,
  TreePage: treepage
  TreePage: treepage,
  CalendarPage: treepage
}
class EditMenu extends Component {
@@ -78,7 +80,7 @@
    btnTabConfig: null,     // 打开新标签按钮配置
    handleMVisible: false,  // 添加或修改菜单模态框(角色权限分配等)
    sysMenu: false,         // 添加或编辑菜单(角色权限分配等)
    optionLibs: [],         // 自定义下拉选项库
    optionLibs: null,       // 自定义下拉选项库
    fstMenuId: null,        // 一级菜单Id
    fstMenuList: null       // 一级菜单列表
  }
@@ -854,6 +856,14 @@
            handleView={this.handleView}
          /> : null
        }
        {this.state.tabview === 'CalendarPage' ?
          <CalendarPageConfig
            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/modalconfig/dragelement/card.jsx
@@ -74,6 +74,29 @@
    }
  }
  let formItem = null
  if (card.type === 'text') {
    formItem = (<Input style={{marginTop: '4px'}} defaultValue={card.initval} />)
  } else if (card.type === 'number') {
    formItem = (<InputNumber defaultValue={card.initval} precision={card.decimal} />)
  } else if (card.type === 'multiselect' || card.type === 'select' || card.type === 'link' || card.type === 'color') {
    formItem = (<Select defaultValue={selectval}></Select>)
  } else if (card.type === 'date') {
    formItem = (<DatePicker defaultValue={card.initval ? moment().subtract(card.initval, 'days') : null} />)
  } else if (card.type === 'datemonth') {
    formItem = (<MonthPicker defaultValue={card.initval ? moment().subtract(card.initval, 'month') : null} />)
  } else if (card.type === 'datetime') {
    formItem = (<DatePicker showTime defaultValue={card.initval ? moment().subtract(card.initval, 'days') : null} />)
  } else if (card.type === 'textarea') {
    formItem = (<TextArea defaultValue={card.initval} autosize={{ minRows: 2, maxRows: 6 }} />)
  } else if (card.type === 'fileupload') {
    formItem = (<Button style={{marginTop: '3px'}}><Icon type="upload" /> 点击上传 </Button>)
  } else if (card.type === 'funcvar') {
    formItem = (<Input style={{marginTop: '4px'}} defaultValue={card.linkfield} />)
  } else if (card.type === 'linkMain') {
    formItem = (<Input style={{marginTop: '4px'}} />)
  }
  return (
    <div className="page-card" style={{ opacity: opacity}}>
      <div ref={node => drag(drop(node))}>
@@ -85,45 +108,11 @@
            <Icon className="edit copy" type="copy" onClick={copy} />
          </div>
          <div className={'ant-col ant-form-item-control-wrapper ant-col-xs-24 ' + wrapCol}>
            {card.type === 'text' &&
              <Input style={{marginTop: '4px'}} defaultValue={card.initval} />
            }
            {card.type === 'number' &&
              <InputNumber defaultValue={card.initval} precision={card.decimal} />
            }
            {(card.type === 'multiselect' || card.type === 'select' || card.type === 'link') &&
              <Select defaultValue={selectval}></Select>
            }
            {card.type === 'date' &&
              <DatePicker defaultValue={card.initval ? moment().subtract(card.initval, 'days') : null} />
            }
            {card.type === 'datemonth' ?
              <MonthPicker defaultValue={card.initval ? moment().subtract(card.initval, 'month') : null} /> : null
            }
            {card.type === 'datetime' &&
              <DatePicker showTime defaultValue={card.initval ? moment().subtract(card.initval, 'days') : null} />
            }
            {card.type === 'textarea' &&
              <TextArea defaultValue={card.initval} autosize={{ minRows: 2, maxRows: 6 }} />
            }
            {card.type === 'fileupload' &&
              <Button style={{marginTop: '3px'}}>
                <Icon type="upload" /> 点击上传
              </Button>
            }
            {card.type === 'funcvar' &&
              <Input style={{marginTop: '4px'}} defaultValue={card.linkfield} />
            }
            {card.type === 'linkMain' &&
              <Input style={{marginTop: '4px'}} />
            }
            {formItem}
            <div className="input-mask"></div>
          </div>
        </div>}
      </div>
      {/* <Icon className="edit" type="edit" onClick={edit} />
      <Icon className="edit close" type="close" onClick={close} />
      <Icon className="edit copy" type="copy" onClick={copy} /> */}
    </div>
  )
}
src/templates/sharecomponent/actioncomponent/dragaction/index.jsx
@@ -125,6 +125,7 @@
        // 导入和导出excel,按钮名称直接为导入、导出
        newcard.label = item.label
        newcard.class = 'border-dgreen'
        newcard.Ot = 'notRequired'
      } else if (item.subType === 'excelOut') {
        newcard.label = item.label
        newcard.intertype = setting.interType
src/templates/sharecomponent/actioncomponent/verifyexcelin/columnform/index.jsx
@@ -75,7 +75,7 @@
        values.uuid = this.state.editItem ? this.state.editItem.uuid : ''
        if (/^Nvarchar/ig.test(values.type)) {
          values.limit = values.type.match(/\d+/)[0]
          values.limit = values.type.match(/\d+/) ? values.type.match(/\d+/)[0] : '20000'
        } else if (/^Decimal/ig.test(values.type)) {
          values.limit = values.type.match(/\d+/ig)[1]
        } else {
@@ -154,7 +154,11 @@
                  <Select.Option value="Nvarchar(20)"> Nvarchar(20) </Select.Option>
                  <Select.Option value="Nvarchar(50)"> Nvarchar(50) </Select.Option>
                  <Select.Option value="Nvarchar(100)"> Nvarchar(100) </Select.Option>
                  <Select.Option value="Nvarchar(256)"> Nvarchar(256) </Select.Option>
                  <Select.Option value="Nvarchar(512)"> Nvarchar(512) </Select.Option>
                  <Select.Option value="Nvarchar(1024)"> Nvarchar(1024) </Select.Option>
                  <Select.Option value="Nvarchar(2048)"> Nvarchar(2048) </Select.Option>
                  <Select.Option value="Nvarchar(max)"> Nvarchar(max) </Select.Option>
                  <Select.Option value="Int"> Int </Select.Option>
                  <Select.Option value="Decimal(18,0)"> Decimal(18,0) </Select.Option>
                  <Select.Option value="Decimal(18,2)"> Decimal(18,2) </Select.Option>
src/templates/sharecomponent/actioncomponent/verifyexcelin/index.jsx
@@ -220,7 +220,7 @@
  UNSAFE_componentWillMount() {
    const { columns, card } = this.props
    let _verify = fromJS(card.verify || {}).toJS()
    let _verify = fromJS(card.verify || {range: 1}).toJS()
    let _columns = _verify.columns || []
    // 同步显示列
src/templates/sharecomponent/datasourcecomponent/index.jsx
New file
@@ -0,0 +1,88 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Icon, Modal } from 'antd'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import VerifyCard from './verifycard'
import './index.scss'
class DataSource extends Component {
  static propTpyes = {
    config: PropTypes.any,
    updateConfig: PropTypes.func
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    sourcelist: [],
    visible: false,
    loading: false,
    setting: null
  }
  UNSAFE_componentWillMount () {
    const { config } = this.props
    this.setState({setting: fromJS(config.setting).toJS()})
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.props), fromJS(nextProps)) || !is(fromJS(this.state), fromJS(nextState))
  }
  editDataSource = () => {
    this.setState({
      visible: true
    })
  }
  verifySubmit = () => {
    const { config } = this.props
    this.setState({loading: true})
    this.verifyRef.submitDataSource().then(res => {
      this.setState({loading: false, visible: false})
      this.props.updateConfig({...config, ...res})
    }, () => {
      this.setState({loading: false})
    })
  }
  render () {
    const { config } = this.props
    const { visible, dict, loading } = this.state
    return (
      <div className="model-datasource">
        <Icon type="setting" onClick={() => this.editDataSource()} />
        <Modal
          wrapClassName="model-datasource-verify-modal popview-modal"
          title={'数据源配置'}
          visible={visible}
          width={'75vw'}
          maskClosable={false}
          style={{minWidth: '900px', maxWidth: '1200px'}}
          okText={dict['model.submit']}
          cancelText={dict['model.cancel']}
          onOk={this.verifySubmit}
          confirmLoading={loading}
          onCancel={() => { this.setState({ visible: false }) }}
          destroyOnClose
        >
          <VerifyCard
            dict={dict}
            config={config}
            menuId={this.props.config.uuid}
            searches={config.search}
            wrappedComponentRef={(inst) => this.verifyRef = inst}
          />
        </Modal>
      </div>
    )
  }
}
export default DataSource
src/templates/sharecomponent/datasourcecomponent/index.scss
New file
@@ -0,0 +1,94 @@
.model-datasource {
  position: absolute;
  right: 7px;
  top: 5px;
  z-index: 1;
  >.anticon-setting {
    font-size: 18px;
    padding: 10px;
  }
  .model-input-group-wrapper {
    padding: 0 20px;
    display: inline-block;
    width: 100%;
    text-align: start;
    vertical-align: top;
    margin-bottom: 15px;
    .model-input-wrapper {
      position: relative;
      display: table;
      width: 100%;
      border-collapse: separate;
      border-spacing: 0;
      .model-input-value {
        display: table-cell;
        width: 100%;
        border: 1px solid #d9d9d9;
        border-radius: 4px 0px 0px 4px;
        overflow: hidden;
        text-overflow:ellipsis;
        white-space: nowrap;
        padding: 2px 10px;
        color: #ffffff;
      }
      .model-input-group-addon {
        display: table-cell;
        width: 1px;
        position: relative;
        padding: 0 11px;
        color: rgba(0, 0, 0, 0.65);
        font-weight: normal;
        font-size: 14px;
        line-height: 1;
        text-align: center;
        background-color: #fafafa;
        border: 1px solid #d9d9d9;
        border-radius: 0px 4px 4px 0px;
        white-space: nowrap;
      }
      .model-input-insert {
        display: table-cell;
        width: 100%;
        border: 1px dotted #d9d9d9;
        border-radius: 4px;
        text-align: center;
        cursor: pointer;
        .anticon-plus {
          padding: 6px;
          font-size: 16px;
          color: rgb(38, 194, 129);
        }
      }
    }
    .anticon-setting {
      margin-right: 5px;
      padding: 6px;
      cursor: pointer;
    }
    .anticon-setting:hover {
      color: #1890ff;
    }
    .anticon-close {
      padding: 6px;
      cursor: pointer;
    }
    .anticon-close:hover {
      color: #ff4d4f;
    }
  }
}
.model-datasource-verify-modal {
  .ant-modal {
    top: 50px;
    .ant-modal-body {
      max-height: calc(100vh - 190px);
    }
  }
}
src/templates/sharecomponent/datasourcecomponent/verifycard/columnform/index.jsx
New file
@@ -0,0 +1,145 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Select, Button, Input } from 'antd'
import './index.scss'
class UniqueForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,       // 字典项
    columnChange: PropTypes.func  // 修改函数
  }
  state = {
    editItem: null // 编辑元素
  }
  edit = (record) => {
    this.setState({
      editItem: record
    })
    this.props.form.setFieldsValue({
      label: record.label,
      field: record.field,
      datatype: record.datatype
    })
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        values.uuid = this.state.editItem ? this.state.editItem.uuid : ''
        this.setState({
          editItem: null
        }, () => {
          this.props.columnChange(values)
        })
        this.props.form.setFieldsValue({
          label: '',
          field: ''
        })
      }
    })
  }
  render() {
    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="verify-form" id="verifycard1">
        <Row gutter={24}>
          <Col span={7}>
            <Form.Item label={'名称'}>
              {getFieldDecorator('label', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + '名称!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col>
          <Col span={7}>
            <Form.Item label={'字段'}>
              {getFieldDecorator('field', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.input'] + '字段!'
                  }
                ]
              })(<Input placeholder="" autoComplete="off" />)}
            </Form.Item>
          </Col>
          <Col span={7}>
            <Form.Item label={'数据类型'}>
              {getFieldDecorator('datatype', {
                initialValue: '',
                rules: [
                  {
                    required: true,
                    message: this.props.dict['form.required.select'] + '数据类型!'
                  }
                ]
              })(
                <Select>
                  <Select.Option value="Nvarchar(50)"> Nvarchar(50) </Select.Option>
                  <Select.Option value="Nvarchar(100)"> Nvarchar(100) </Select.Option>
                  <Select.Option value="Nvarchar(512)"> Nvarchar(512) </Select.Option>
                  <Select.Option value="Nvarchar(1024)"> Nvarchar(1024) </Select.Option>
                  <Select.Option value="Nvarchar(2048)"> Nvarchar(2048) </Select.Option>
                  <Select.Option value="Nvarchar(max)"> Nvarchar(max) </Select.Option>
                  <Select.Option value="Int"> Int </Select.Option>
                  <Select.Option value="Decimal(18,0)"> Decimal(18,0) </Select.Option>
                  <Select.Option value="Decimal(18,1)"> Decimal(18,1) </Select.Option>
                  <Select.Option value="Decimal(18,2)"> Decimal(18,2) </Select.Option>
                  <Select.Option value="Decimal(18,3)"> Decimal(18,3) </Select.Option>
                  <Select.Option value="Decimal(18,4)"> Decimal(18,4) </Select.Option>
                  <Select.Option value="Decimal(18,5)"> Decimal(18,5) </Select.Option>
                  <Select.Option value="Decimal(18,6)"> Decimal(18,6) </Select.Option>
                  <Select.Option value="Decimal(18,7)"> Decimal(18,7) </Select.Option>
                  <Select.Option value="Decimal(18,8)"> Decimal(18,8) </Select.Option>
                  <Select.Option value="Decimal(18,9)"> Decimal(18,9) </Select.Option>
                  <Select.Option value="Decimal(18,10)"> Decimal(18,10) </Select.Option>
                  <Select.Option value="Decimal(18,11)"> Decimal(18,11) </Select.Option>
                  <Select.Option value="Decimal(18,12)"> Decimal(18,12) </Select.Option>
                  <Select.Option value="Decimal(18,13)"> Decimal(18,13) </Select.Option>
                  <Select.Option value="Decimal(18,14)"> Decimal(18,14) </Select.Option>
                  <Select.Option value="Decimal(18,15)"> Decimal(18,15) </Select.Option>
                  <Select.Option value="Decimal(18,16)"> Decimal(18,16) </Select.Option>
                  <Select.Option value="Decimal(18,17)"> Decimal(18,17) </Select.Option>
                  <Select.Option value="Decimal(18,18)"> Decimal(18,18) </Select.Option>
                  {/* <Select.Option value="date"> date </Select.Option> */}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={3} className="add">
            <Button onClick={this.handleConfirm} type="primary" className="mk-green">
              保存
            </Button>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(UniqueForm)
src/templates/sharecomponent/datasourcecomponent/verifycard/columnform/index.scss
src/templates/sharecomponent/datasourcecomponent/verifycard/customscript/index.jsx
New file
@@ -0,0 +1,231 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Button, notification, Select } from 'antd'
import Utils from '@/utils/utils.js'
import CodeMirror from '@/templates/zshare/codemirror'
import './index.scss'
class CustomForm extends Component {
  static propTpyes = {
    type: PropTypes.string,         // 菜单类型
    dict: PropTypes.object,         // 字典项
    setting: PropTypes.object,      // 设置
    searches: PropTypes.array,      // 搜索条件
    swhere: PropTypes.string,       // where条件
    arr_field: PropTypes.string,    // 列字段
    regoptions: PropTypes.array,    // 正则替换
    systemScripts: PropTypes.array, // 系统脚本
    scriptSubmit: PropTypes.func,   // 脚本验证后提交
    scriptsChange: PropTypes.func   // 脚本验证
  }
  state = {
    editItem: null,
    loading: false,
    usefulFields: ''
  }
  UNSAFE_componentWillMount() {
    const { searches } = this.props
    let _usefulFields = []
    searches.forEach(item => {
      if (!item.field) return
      if (item.type === 'group') {
        if (item.transfer === 'true') {
          _usefulFields.push(item.field)
        }
        _usefulFields.push(item.datefield)
        _usefulFields.push(item.datefield + '1')
      } else if (['dateweek', 'datemonth', 'daterange'].includes(item.type)) {
        _usefulFields.push(item.field)
        _usefulFields.push(item.field + '1')
      } else if (_usefulFields.includes(item.field)) {
        _usefulFields.push(item.field + '1')
      } else {
        _usefulFields.push(item.field)
      }
    })
    this.setState({
      usefulFields: _usefulFields.join(', ')
    })
  }
  edit = (record) => {
    this.setState({
      editItem: record
    })
    this.props.form.setFieldsValue({
      sql: record.sql
    })
  }
  handleCancel = () => {
    this.setState({
      editItem: null
    })
    this.props.form.setFieldsValue({
      sql: ''
    })
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    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
        }
        this.setState({loading: true})
        this.props.scriptsChange(values).then(() => {
          this.setState({
            editItem: null,
            loading: false
          })
          this.props.form.setFieldsValue({
            sql: ''
          })
          this.props.scriptSubmit(values)
        })
      }
    })
  }
  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 { usefulFields } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="modal-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{usefulFields ? ', ' + usefulFields : ''}
            </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!'
                  }
                ]
              })(<CodeMirror />)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(CustomForm)
src/templates/sharecomponent/datasourcecomponent/verifycard/customscript/index.scss
New file
@@ -0,0 +1,34 @@
.modal-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;
    }
    .CodeMirror {
      height: 350px;
    }
  }
}
src/templates/sharecomponent/datasourcecomponent/verifycard/index.jsx
New file
@@ -0,0 +1,524 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Tabs, Table, Popconfirm, Icon, notification, Modal, Typography, Spin } from 'antd'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import ColForm from './columnform'
import CustomScriptsForm from './customscript'
import SettingForm from './settingform'
import SettingUtils from './utils'
import './index.scss'
const { TabPane } = Tabs
const { Paragraph } = Typography
class VerifyCard extends Component {
  static propTpyes = {
    dict: PropTypes.object,     // 字典项
    config: PropTypes.object,   // 数据源信息
    menuId: PropTypes.string,   // 菜单Id
    searches: PropTypes.array,  // 搜索条件
  }
  state = {
    columns: [],
    activeKey: 'setting',
    loading: false,
    initsql: '',          // sql验证时变量声明及赋值
    usefulfields: '',
    defaultsql: '',         // 默认Sql
    systemScripts: [{
      name: '默认sql',
      value: ''
    }],
    colColumns: [
      {
        title: '名称',
        dataIndex: 'label',
        width: '25%'
      },
      {
        title: '字段',
        dataIndex: 'field',
        width: '25%'
      },
      {
        title: '数据类型',
        dataIndex: 'datatype',
        width: '25%',
      },
      {
        title: '操作',
        align: 'center',
        width: '25%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record, 'columns')} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <Popconfirm
              title={this.props.dict['model.query.delete']}
              okText={this.props.dict['model.confirm']}
              cancelText={this.props.dict['model.cancel']}
              onConfirm={() => this.deleteColumn(record)
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => (
          <Paragraph copyable ellipsis={{ rows: 5, expandable: true }}>{text}</Paragraph>
        )
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '20%',
        render: (text, record) => record.status === 'false' ?
          (
            <div>
              {this.props.dict['model.status.forbidden']}
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              {this.props.dict['model.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, 'scripts')} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <span className="operation-btn" onClick={() => this.handleUpDown(record, 'up')} style={{color: '#1890ff'}}><Icon type="arrow-up" /></span>
            <span className="operation-btn" onClick={() => this.handleUpDown(record, 'down')} style={{color: '#ff4d4f'}}><Icon type="arrow-down" /></span>
            <span className="operation-btn" title={this.props.dict['model.status.change']} onClick={() => this.handleStatus(record)} style={{color: '#8E44AD'}}><Icon type="swap" /></span>
            <Popconfirm
              title={this.props.dict['model.query.delete']}
              okText={this.props.dict['model.confirm']}
              cancelText={this.props.dict['model.cancel']}
              onConfirm={() => this.deleteScript(record)
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  UNSAFE_componentWillMount() {
    const { config } = this.props
    this.setState({
      columns: fromJS(config.columns).toJS(),
      setting: fromJS(config.setting).toJS(),
      scripts: fromJS(config.scripts).toJS()
    })
    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)
    _sParam.open_key = Utils.encrypt(_sParam.secretkey, _sParam.timestamp, true) // 云端数据验证
    Api.getSystemConfig(_sParam).then(res => {
      if (res.status) {
        let _scripts = []
        res.data.forEach(item => {
          let _item = {
            name: item.funcname,
            value: Utils.formatOptions(item.longparam, true)
          }
          _scripts.push(_item)
        })
        this.setState({
          systemScripts: [...this.state.systemScripts, ..._scripts]
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  columnChange = (values) => {
    let columns = fromJS(this.state.columns).toJS()
    if (values.uuid) {
      columns = columns.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      values.uuid = Utils.getuuid()
      columns.push(values)
    }
    this.setState({ columns })
  }
  deleteColumn = (record) => {
    this.setState({ columns: this.state.columns.filter(item => item.uuid !== record.uuid) })
  }
  deleteScript = (record) => {
    this.setState({ scripts: this.state.scripts.filter(item => item.uuid !== record.uuid) })
  }
  handleEdit = (record, type) => {
    if (type === 'scripts') {
      this.scriptsForm.edit(record)
    } else if (type === 'columns') {
      this.contrastForm.edit(record)
    }
    let node = document.getElementById('model-verify-card-box-tab').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)
    }
  }
  handleStatus = (record) => {
    let scripts = fromJS(this.state.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({ scripts })
  }
  handleUpDown = (record, direction) => {
    let scripts = fromJS(this.state.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({ scripts })
  }
  scriptsChange = (values) => {
    let scripts = fromJS(this.state.scripts).toJS()
    if (values.uuid) {
      scripts = scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      scripts.push(values)
    }
    return new Promise((resolve, reject) => {
      this.sqlverify(resolve, reject, false, scripts)
    })
  }
  scriptSubmit = (values) => {
    let scripts = fromJS(this.state.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({ scripts })
  }
  changeTab = (val) => {
    const { activeKey } = this.state
    this.setState({loading: true})
    if (activeKey === 'setting') {
      this.settingForm.handleConfirm().then(res => {
        this.setState({
          setting: res
        }, () => {
          this.sqlverify(() => { // 验证成功
            this.setState({
              activeKey: val,
              loading: false
            })
          }, () => {             // 验证失败
            this.setState({
              loading: false
            })
          }, true)
        })
      }, () => {
        this.setState({loading: false})
      })
    } else if (activeKey === 'columns') {
      this.sqlverify(() => { // 验证成功
        this.setState({
          activeKey: val,
          loading: false
        })
      }, () => {             // 验证失败
        this.setState({
          loading: false
        })
      }, true)
    } else if (activeKey === 'scripts') {
      let _loading = false
      if (this.scriptsForm && this.scriptsForm.state.editItem) {
        _loading = true
      } else if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
        _loading = true
      }
      if (_loading) {
        notification.warning({
          top: 92,
          message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
          duration: 5
        })
        this.setState({
          loading: false
        })
        return
      }
      this.sqlverify(() => { // 验证成功
        this.setState({
          activeKey: val,
          loading: false
        })
      }, () => {             // 验证失败
        this.setState({
          loading: false
        })
      }, true)
    }
  }
  submitDataSource = () => {
    const { activeKey, setting, columns, scripts } = this.state
    return new Promise((resolve, reject) => {
      if (activeKey === 'setting') {
        this.settingForm.handleConfirm().then(res => {
          this.setState({
            setting: res
          }, () => {
            this.sqlverify(() => { resolve({setting: res, columns, scripts }) }, reject, false)
          })
        }, () => {
          reject()
        })
      } else if (activeKey === 'columns') {
        this.sqlverify(() => { resolve({setting, columns, scripts }) }, reject, false)
      } else if (activeKey === 'scripts') {
        let _loading = false
        if (this.scriptsForm && this.scriptsForm.state.editItem) {
          _loading = true
        } else if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql')) {
          _loading = true
        }
        if (_loading) {
          notification.warning({
            top: 92,
            message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
            duration: 5
          })
          reject()
          return
        }
        this.sqlverify(() => { resolve({setting, columns, scripts }) }, reject, false)
      }
    })
  }
  sqlverify = (resolve, reject, change = false, testScripts) => {
    const { searches } = this.props
    const { columns, setting, scripts } = this.state
    let _scripts = scripts.filter(item => item.status !== 'false')
    if (testScripts) {
      _scripts = testScripts.filter(item => item.status !== 'false')
    }
    if (!change && setting.interType === 'inner' && !setting.innerFunc && setting.execute === 'false' && _scripts.length === 0) {
      notification.warning({
        top: 92,
        message: '不执行默认sql时,请添加自定义脚本!',
        duration: 5
      })
      reject()
      return
    }
    if ((setting.interType === 'inner' && !setting.innerFunc && setting.execute !== 'false') || _scripts.length > 0) {
      let param = {
        func: 's_debug_sql',
        LText: SettingUtils.getDebugSql(setting, _scripts, columns, searches)
      }
      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()
        } else {
          reject()
          Modal.error({
            title: result.message
          })
        }
      })
    } else {
      resolve()
    }
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  render() {
    const { columns, setting, scripts, colColumns, scriptsColumns, activeKey, loading } = this.state
    return (
      <div id="model-verify-card-box-tab">
        {loading && <Spin size="large" />}
        <Tabs activeKey={activeKey} className="verify-card-box" onChange={this.changeTab}>
          <TabPane tab="数据源" key="setting">
            <SettingForm
              menuId={this.props.menuId}
              dict={this.props.dict}
              columns={columns}
              setting={setting}
              scripts={scripts}
              wrappedComponentRef={(inst) => this.settingForm = inst}
            />
          </TabPane>
          <TabPane tab="字段集" key="columns">
            <ColForm
              dict={this.props.dict}
              columnChange={this.columnChange}
              wrappedComponentRef={(inst) => this.contrastForm = inst}
            />
            <Table
              bordered
              rowKey="uuid"
              className="custom-table"
              dataSource={columns}
              columns={colColumns}
              pagination={false}
            />
          </TabPane>
          <TabPane tab="自定义脚本" key="scripts">
            <CustomScriptsForm
              setting={setting}
              searches={this.props.searches}
              initsql={this.state.initsql}
              dict={this.props.dict}
              customScripts={scripts}
              systemScripts={this.state.systemScripts}
              scriptsChange={this.scriptsChange}
              scriptSubmit={this.scriptSubmit}
              wrappedComponentRef={(inst) => this.scriptsForm = inst}
            />
            <Table
              bordered
              rowKey="uuid"
              className="custom-table"
              dataSource={scripts}
              columns={scriptsColumns}
              pagination={false}
            />
          </TabPane>
        </Tabs>
      </div>
    )
  }
}
export default Form.create()(VerifyCard)
src/templates/sharecomponent/datasourcecomponent/verifycard/index.scss
New file
@@ -0,0 +1,74 @@
#model-verify-card-box-tab {
  .ant-spin {
    position: absolute;
    left: calc(50% - 16px);
    top: 220px;
    z-index: 1;
  }
  .verify-card-box {
    .ant-tabs-nav-scroll {
      text-align: center;
    }
    .ant-tabs-content {
      min-height: 40vh;
    }
    table tr td {
      word-wrap: break-word;
      word-break: break-word;
    }
    .verify-form {
      .ant-input-number {
        width: 100%;
      }
      .sql {
        .ant-col-sm-8 {
          width: 10.5%;
        }
        .ant-col-sm-16 {
          width: 89.5%;
          padding-top: 4px;
        }
      }
      .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%;
        }
      }
      .add {
        padding-top: 4px;
      }
      .anticon-question-circle {
        color: #c49f47;
        margin-right: 3px;
      }
    }
    .custom-table .ant-empty {
      margin: 20px 8px!important;
    }
    .errorval {
      display: inline-block;
      width: 30px;
    }
    .operation-btn {
      display: inline-block;
      font-size: 16px;
      padding: 0 5px;
      cursor: pointer;
    }
  }
}
src/templates/sharecomponent/datasourcecomponent/verifycard/settingform/index.jsx
New file
@@ -0,0 +1,278 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Select, Tooltip, Icon, notification } from 'antd'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import CodeMirror from '@/templates/zshare/codemirror'
import './index.scss'
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,      // 字典项
    menuId: PropTypes.string,    // 菜单Id
    setting: PropTypes.object,   // 数据源配置
    columns: PropTypes.array,    // 列设置
    scripts: PropTypes.array,    // 自定义脚本
  }
  state = {
    interType: this.props.setting.interType || 'inner',
  }
  handleConfirm = () => {
    const { setting } = this.props
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          // 数据源前端验证
          if (values.interType === 'inner' && !values.innerFunc && values.execute !== 'false' && !values.dataresource) {
            notification.warning({
              top: 92,
              message: '请填写内部函数或数据源!',
              duration: 5
            })
            reject()
            return
          } else if (values.interType === 'inner' && !values.innerFunc && values.execute !== 'false' && values.dataresource) {
            let _quot = values.dataresource.match(/'{1}/g)
            let _lparen = values.dataresource.match(/\({1}/g)
            let _rparen = values.dataresource.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: '数据源中\'必须成对出现',
                duration: 5
              })
              reject()
              return
            } else if (_lparen !== _rparen) {
              notification.warning({
                top: 92,
                message: '数据源中()必须成对出现',
                duration: 5
              })
              reject()
              return
            } else if (/--/ig.test(values.dataresource)) {
              notification.warning({
                top: 92,
                message: '数据源中,不可出现字符 -- ,注释请用 /*内容*/',
                duration: 5
              })
              reject()
              return
            }
            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.execute !== 'false' &&
            /[^\s]+\s+[^\s]+/ig.test(values.dataresource) && setting.dataresource !== values.dataresource
          ) {
            let param = {
              func: 's_DataSrc_Save',
              LText: values.dataresource,
              MenuID: this.props.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)
          }
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  onRadioChange = (e, key) => {
    let value = e.target.value
    if (key === 'interType') {
      this.setState({
        interType: value
      })
    }
  }
  render() {
    const { columns, setting } = this.props
    const { getFieldDecorator } = this.props.form
    const { interType } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-datasource-setting-form-box">
        <Form {...formItemLayout} className="model-setting-form">
          <Row gutter={24}>
            <Col span={8}>
              <Form.Item label="表名">
                {getFieldDecorator('tableName', {
                  initialValue: setting.tableName,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '表名!'
                    },
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col>
            <Col span={8}>
              <Form.Item label="接口类型">
                {getFieldDecorator('interType', {
                  initialValue: interType,
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.select'] + '接口类型!'
                    },
                  ]
                })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e, 'interType')}}>
                  <Radio value="inner">内部</Radio>
                  <Radio value="outer">外部</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            {interType === 'inner' ? <Col span={8}>
              <Form.Item label="内部函数">
                {getFieldDecorator('innerFunc', {
                  initialValue: setting.innerFunc || '',
                  rules: [
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col span={8}>
              <Form.Item label="接口地址">
                {getFieldDecorator('interface', {
                  initialValue: setting.interface || '',
                  rules: [
                    {
                      required: true,
                      message: this.props.dict['form.required.input'] + '接口地址!'
                    },
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'outer' ? <Col span={8}>
              <Form.Item label="外部函数">
                {getFieldDecorator('outerFunc', {
                  initialValue: setting.outerFunc || '',
                  rules: [
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            {interType === 'inner' ? <Col span={24} className="data-source" style={{paddingLeft: '7px'}}>
              <Form.Item labelCol={{xs: { span: 24 }, sm: { span: 2 }}} wrapperCol={ {xs: { span: 24 }, sm: { span: 22 }} } label={
                <Tooltip placement="topLeft" title={'使用系统函数时,需填写数据源。注:数据权限替换符 $@ -> /* 或 \'\'、 @$ -> */ 或 \'\''}>
                  <Icon type="question-circle" />
                  数据源
                </Tooltip>
              }>
                {getFieldDecorator('dataresource', {
                  initialValue: setting.dataresource || ''
                })(<CodeMirror />)}
              </Form.Item>
            </Col> : null}
            {interType === 'inner' ? <Col span={8}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={'查询时,搜索条件以where条件拼接进入sql,统计时,将数据源中以“@+搜索字段+@”的内容,以搜索条件中的值进行替换后,提交查询,注:查询类型仅在使用系统函数时有效。'}>
                  <Icon type="question-circle" />
                  查询类型
                </Tooltip>
              }>
                {getFieldDecorator('queryType', {
                  initialValue: setting.queryType || 'query'
                })(
                <Radio.Group>
                  <Radio value="query">查询</Radio>
                  <Radio value="statistics">统计</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            <Col span={8}>
              <Form.Item label="主键">
                {getFieldDecorator('primaryKey', {
                  initialValue: setting.primaryKey || ''
                })(
                  <Select onChange={(value) => {this.selectChange('primaryKey', value)}}>
                    {columns.map((option, i) =>
                      <Select.Option key={i} value={option.value}>
                        {option.text}
                      </Select.Option>
                    )}
                  </Select>
                )}
              </Form.Item>
            </Col>
            {interType === 'inner' ? <Col span={8}>
              <Form.Item label="默认sql">
                {getFieldDecorator('execute', {
                  initialValue: setting.execute || 'true'
                })(
                <Radio.Group>
                  <Radio value="true">执行</Radio>
                  <Radio value="false">不执行</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col> : null}
            <Col span={8}>
              <Form.Item label="初始化">
                {getFieldDecorator('onload', {
                  initialValue: setting.onload || 'true'
                })(
                <Radio.Group>
                  <Radio value="true">加载数据</Radio>
                  <Radio value="false">不加载数据</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/templates/sharecomponent/datasourcecomponent/verifycard/settingform/index.scss
New file
@@ -0,0 +1,22 @@
.model-datasource-setting-form-box {
  position: relative;
  .model-setting-form {
    .data-source {
      .ant-form-item-label {
        width: 11%;
      }
      .ant-form-item-control-wrapper {
        width: 89%;
      }
      .CodeMirror {
        height: 150px;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
  }
}
src/templates/sharecomponent/datasourcecomponent/verifycard/settingform/utils.jsx
New file
@@ -0,0 +1,84 @@
export default class SettingUtils {
  /**
   * @description 生成页面查询语句
   * @return {String}  arr_field     显示列字段
   * @return {String}  search        搜索条件
   * @return {Object}  setting       页面设置
   * @return {Array}   regoptions    搜索条件正则替换
   */
  static getDebugSql (setting, arr_field, regoptions, search) {
    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, '')
    }
    // 正则替换
    let _regoptions = regoptions.map(item => {
      return {
        reg: new RegExp('@' + item.key + '@', 'ig'),
        value: `'${item.value}'`
      }
    })
    let _search = search
    if (setting.queryType === 'statistics' && _dataresource) {
      _regoptions.forEach(item => {
        _dataresource = _dataresource.replace(item.reg, item.value)
      })
      _search = ''
    }
    if (_customScript) {
      _regoptions.push({
        reg: new RegExp('@orderBy@', 'ig'),
        value: setting.order
      })
      if (setting.laypage !== 'false') {
        _regoptions.push({
          reg: new RegExp('@pageSize@', 'ig'),
          value: 10
        }, {
          reg: new RegExp('@pageIndex@', 'ig'),
          value: 1
        })
      }
      _regoptions.forEach(item => {
        _customScript = _customScript.replace(item.reg, item.value)
      })
    }
    // 数据源处理, 存在显示列时
    if (arr_field && _dataresource) {
      if (/\s/.test(_dataresource)) {
        _dataresource = '(' + _dataresource + ') tb'
      }
      _dataresource = `select ${setting.laypage !== 'false' ?  'top 10' : ''} ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${setting.order}) as rows from ${_dataresource} ${_search}) tmptable ${setting.laypage !== 'false' ?  'where rows > 0' : ''} 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/sharecomponent/datasourcecomponent/verifycard/utils.jsx
New file
@@ -0,0 +1,99 @@
export default class SettingUtils {
  /**
   * @description 生成页面查询语句
   * @return {String}  scripts       自定义脚本
   * @return {String}  searches      搜索条件
   * @return {Object}  setting       页面设置
   * @return {Array}   columns       显示字段
   */
  static getDebugSql (setting, scripts, columns, searches) {
    let sql = ''
    let _dataresource = ''
    let _customScript = ''
    let arr_field = columns.map(item => item.field).join(',')
    if (scripts.length > 0) {
      scripts.forEach(item => {
        _customScript += `
          ${item.sql}
        `
      })
    }
    if (_customScript) {
      _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
    if (setting.interType === 'inner' && !setting.innerFunc && setting.execute !== 'false') {
      _dataresource = setting.dataresource
    }
    if (_dataresource) {
      _dataresource = _dataresource.replace(/@\$|\$@/ig, '')
    }
    if (_customScript) {
      _customScript = _customScript.replace(/@\$|\$@/ig, '')
    }
    // 正则替换
    let _regoptions = searches.map(item => {
      return {
        reg: new RegExp('@' + item.key + '@', 'ig'),
        value: `'${item.value}'`
      }
    })
    let _search = ''
    if (setting.queryType === 'statistics' && _dataresource) {
      _regoptions.forEach(item => {
        _dataresource = _dataresource.replace(item.reg, item.value)
      })
      _search = ''
    }
    if (_customScript) {
      _regoptions.push({
        reg: new RegExp('@orderBy@', 'ig'),
        value: setting.order
      })
      if (setting.laypage !== 'false') {
        _regoptions.push({
          reg: new RegExp('@pageSize@', 'ig'),
          value: 10
        }, {
          reg: new RegExp('@pageIndex@', 'ig'),
          value: 1
        })
      }
      _regoptions.forEach(item => {
        _customScript = _customScript.replace(item.reg, item.value)
      })
    }
    // 数据源处理, 存在显示列时
    if (arr_field && _dataresource) {
      if (/\s/.test(_dataresource)) {
        _dataresource = '(' + _dataresource + ') tb'
      }
      _dataresource = `select ${setting.laypage !== 'false' ?  'top 10' : ''} ${arr_field} from (select ${arr_field} ,ROW_NUMBER() over(order by ${setting.order}) as rows from ${_dataresource} ${_search}) tmptable ${setting.laypage !== 'false' ?  'where rows > 0' : ''} 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/sharecomponent/tabscomponent/index.jsx
@@ -250,7 +250,7 @@
        _this.setState({
          tabgroups: tabgroups
        }, () => {
          _this.props.updatetabs({...config, tabgroups: tabgroups}, [card])
          _this.props.updatetabs({...config, tabgroups: tabgroups})
        })
      },
      onCancel() {}
src/templates/zshare/formconfig.jsx
@@ -2505,7 +2505,8 @@
      }, {
        value: 'line-chart',
        text: 'line-chart'
      }]
      }],
      forbid: type === 'CalendarPage',
    },
    {
      type: 'select',
@@ -2513,7 +2514,8 @@
      label: Formdict['header.form.supTab'],
      initVal: supMenu,
      required: false,
      options: menus
      options: menus,
      forbid: type === 'CalendarPage',
    },
    {
      type: 'mutilselect',
@@ -2522,7 +2524,8 @@
      tooltip: '如果子标签中含有刷新同级标签的按钮,在此处添加需要刷新的标签。',
      initVal: equalTab,
      required: false,
      options: equalTabs
      options: equalTabs,
      forbid: type === 'CalendarPage',
    },
    {
      type: 'text',
@@ -2550,7 +2553,7 @@
      initVal: card.searchPass || 'false',
      tooltip: '使用主表搜索条件时,主表的搜索条件会传入子表中。',
      required: false,
      forbid: type !== 'CommonTable',
      forbid: type !== 'CommonTable' && type !== 'CalendarPage',
      options: [{
        value: 'true',
        text: '使用'
src/templates/zshare/modalform/index.jsx
@@ -19,7 +19,7 @@
  datemonth: ['label', 'field', 'initval', 'type', 'readonly', 'required', 'hidden', 'readin', 'blacklist'],
  datetime: ['label', 'field', 'initval', 'type', 'readonly', 'required', 'hidden', 'readin', 'blacklist'],
  textarea: ['label', 'field', 'initval', 'type', 'readonly', 'required', 'hidden', 'readin', 'blacklist', 'fieldlength', 'maxRows', 'encryption', 'interception'],
  color: ['label', 'field', 'type', 'blacklist', 'required', 'hidden', 'readin'],
  color: ['label', 'field', 'type', 'blacklist', 'readonly', 'required', 'hidden', 'readin'],
  funcvar: ['label', 'field', 'type', 'blacklist', 'hidden'],
  linkMain: ['label', 'field', 'type', 'readonly', 'required', 'hidden', 'fieldlength', 'blacklist']
}
src/utils/option.js
@@ -70,6 +70,13 @@
    isSystem: true
  },
  {
    title: '日历',
    type: 'CalendarPage',
    url: treepage,
    baseconfig: '',
    isSystem: true
  },
  {
    title: '角色权限分配',
    type: 'RolePermission',
    url: rolemanage,
@@ -301,6 +308,20 @@
  text: '白底紫框'
}]
export const calendarColors = [
  {name: 'black', value: '#ffffff'},
  {name: 'red', value: '#ffffff'},
  {name: 'orange', value: '#ffffff'},
  {name: 'yellow', value: 'rgba(0,0,0,.65)'},
  {name: 'green', value: '#ffffff'},
  {name: 'lightgreen', value: 'rgba(0,0,0,.65)'},
  {name: 'cyan', value: 'rgba(0,0,0,.65)'},
  {name: 'blue', value: '#ffffff'},
  {name: 'purple', value: '#ffffff'},
  {name: 'white', value: 'rgba(0,0,0,.65)'}
]
// 显示列标记色系
export const colorTransform = {
  'dust-red-1': '#fff1f0',
  'dust-red-2': '#ffccc7',
@@ -439,15 +460,10 @@
    value: 'DustRed',
    label: '薄暮',
    children: [
      // { value: 'dust-red-1', label: 'dust-red-1' },
      { value: 'dust-red-2', label: 'dust-red-2' },
      // { value: 'dust-red-3', label: 'dust-red-3' },
      { value: 'dust-red-4', label: 'dust-red-4' },
      // { value: 'dust-red-5', label: 'dust-red-5' },
      { value: 'dust-red-6', label: 'dust-red-6' },
      // { value: 'dust-red-7', label: 'dust-red-7' },
      { value: 'dust-red-8', label: 'dust-red-8' },
      // { value: 'dust-red-9', label: 'dust-red-9' },
      { value: 'dust-red-10', label: 'dust-red-10' }
    ]
  },
@@ -455,15 +471,10 @@
    value: 'Volcano',
    label: '火山',
    children: [
      // { value: 'volcano-1', label: 'volcano-1' },
      { value: 'volcano-2', label: 'volcano-2' },
      // { value: 'volcano-3', label: 'volcano-3' },
      { value: 'volcano-4', label: 'volcano-4' },
      // { value: 'volcano-5', label: 'volcano-5' },
      { value: 'volcano-6', label: 'volcano-6' },
      // { value: 'volcano-7', label: 'volcano-7' },
      { value: 'volcano-8', label: 'volcano-8' },
      // { value: 'volcano-9', label: 'volcano-9' },
      { value: 'volcano-10', label: 'volcano-10' }
    ],
  },
@@ -471,15 +482,10 @@
    value: 'SunsetOrange',
    label: '日暮',
    children: [
      // { value: 'orange-1', label: 'orange-1' },
      { value: 'orange-2', label: 'orange-2' },
      // { value: 'orange-3', label: 'orange-3' },
      { value: 'orange-4', label: 'orange-4' },
      // { value: 'orange-5', label: 'orange-5' },
      { value: 'orange-6', label: 'orange-6' },
      // { value: 'orange-7', label: 'orange-7' },
      { value: 'orange-8', label: 'orange-8' },
      // { value: 'orange-9', label: 'orange-9' },
      { value: 'orange-10', label: 'orange-10' }
    ]
  },
@@ -487,15 +493,10 @@
    value: 'CalendulaGold',
    label: '金盏花',
    children: [
      // { value: 'gold-1', label: 'gold-1' },
      { value: 'gold-2', label: 'gold-2' },
      // { value: 'gold-3', label: 'gold-3' },
      { value: 'gold-4', label: 'gold-4' },
      // { value: 'gold-5', label: 'gold-5' },
      { value: 'gold-6', label: 'gold-6' },
      // { value: 'gold-7', label: 'gold-7' },
      { value: 'gold-8', label: 'gold-8' },
      // { value: 'gold-9', label: 'gold-9' },
      { value: 'gold-10', label: 'gold-10' }
    ]
  },
@@ -503,15 +504,10 @@
    value: 'SunriseYellow',
    label: '日出',
    children: [
      // { value: 'yellow-1', label: 'yellow-1' },
      { value: 'yellow-2', label: 'yellow-2' },
      // { value: 'yellow-3', label: 'yellow-3' },
      { value: 'yellow-4', label: 'yellow-4' },
      // { value: 'yellow-5', label: 'yellow-5' },
      { value: 'yellow-6', label: 'yellow-6' },
      // { value: 'yellow-7', label: 'yellow-7' },
      { value: 'yellow-8', label: 'yellow-8' },
      // { value: 'yellow-9', label: 'yellow-9' },
      { value: 'yellow-10', label: 'yellow-10' }
    ]
  },
@@ -519,15 +515,10 @@
    value: 'Lime',
    label: '青柠',
    children: [
      // { value: 'lime-1', label: 'lime-1' },
      { value: 'lime-2', label: 'lime-2' },
      // { value: 'lime-3', label: 'lime-3' },
      { value: 'lime-4', label: 'lime-4' },
      // { value: 'lime-5', label: 'lime-5' },
      { value: 'lime-6', label: 'lime-6' },
      // { value: 'lime-7', label: 'lime-7' },
      { value: 'lime-8', label: 'lime-8' },
      // { value: 'lime-9', label: 'lime-9' },
      { value: 'lime-10', label: 'lime-10' }
    ]
  },
@@ -535,15 +526,10 @@
    value: 'PolarGreen',
    label: '极光绿',
    children: [
      // { value: 'green-1', label: 'green-1' },
      { value: 'green-2', label: 'green-2' },
      // { value: 'green-3', label: 'green-3' },
      { value: 'green-4', label: 'green-4' },
      // { value: 'green-5', label: 'green-5' },
      { value: 'green-6', label: 'green-6' },
      // { value: 'green-7', label: 'green-7' },
      { value: 'green-8', label: 'green-8' },
      // { value: 'green-9', label: 'green-9' },
      { value: 'green-10', label: 'green-10' }
    ]
  },
@@ -551,15 +537,10 @@
    value: 'Cyan',
    label: '明青',
    children: [
      // { value: 'cyan-1', label: 'cyan-1' },
      { value: 'cyan-2', label: 'cyan-2' },
      // { value: 'cyan-3', label: 'cyan-3' },
      { value: 'cyan-4', label: 'cyan-4' },
      // { value: 'cyan-5', label: 'cyan-5' },
      { value: 'cyan-6', label: 'cyan-6' },
      // { value: 'cyan-7', label: 'cyan-7' },
      { value: 'cyan-8', label: 'cyan-8' },
      // { value: 'cyan-9', label: 'cyan-9' },
      { value: 'cyan-10', label: 'cyan-10' }
    ]
  },
@@ -567,15 +548,10 @@
    value: 'DaybreakBlue',
    label: '拂晓蓝',
    children: [
      // { value: 'blue-1', label: 'blue-1' },
      { value: 'blue-2', label: 'blue-2' },
      // { value: 'blue-3', label: 'blue-3' },
      { value: 'blue-4', label: 'blue-4' },
      // { value: 'blue-5', label: 'blue-5' },
      { value: 'blue-6', label: 'blue-6' },
      // { value: 'blue-7', label: 'blue-7' },
      { value: 'blue-8', label: 'blue-8' },
      // { value: 'blue-9', label: 'blue-9' },
      { value: 'blue-10', label: 'blue-10' }
    ]
  },
@@ -583,15 +559,10 @@
    value: 'GeekBlue',
    label: '极客蓝',
    children: [
      // { value: 'geekblue-1', label: 'geekblue-1' },
      { value: 'geekblue-2', label: 'geekblue-2' },
      // { value: 'geekblue-3', label: 'geekblue-3' },
      { value: 'geekblue-4', label: 'geekblue-4' },
      // { value: 'geekblue-5', label: 'geekblue-5' },
      { value: 'geekblue-6', label: 'geekblue-6' },
      // { value: 'geekblue-7', label: 'geekblue-7' },
      { value: 'geekblue-8', label: 'geekblue-8' },
      // { value: 'geekblue-9', label: 'geekblue-9' },
      { value: 'geekblue-10', label: 'geekblue-10' }
    ]
  },
@@ -599,15 +570,10 @@
    value: 'GoldenPurple',
    label: '酱紫',
    children: [
      // { value: 'purple-1', label: 'purple-1' },
      { value: 'purple-2', label: 'purple-2' },
      // { value: 'purple-3', label: 'purple-3' },
      { value: 'purple-4', label: 'purple-4' },
      // { value: 'purple-5', label: 'purple-5' },
      { value: 'purple-6', label: 'purple-6' },
      // { value: 'purple-7', label: 'purple-7' },
      { value: 'purple-8', label: 'purple-8' },
      // { value: 'purple-9', label: 'purple-9' },
      { value: 'purple-10', label: 'purple-10' }
    ]
  },
@@ -615,15 +581,10 @@
    value: 'Magenta',
    label: '法式洋红',
    children: [
      // { value: 'magenta-1', label: 'magenta-1' },
      { value: 'magenta-2', label: 'magenta-2' },
      // { value: 'magenta-3', label: 'magenta-3' },
      { value: 'magenta-4', label: 'magenta-4' },
      // { value: 'magenta-5', label: 'magenta-5' },
      { value: 'magenta-6', label: 'magenta-6' },
      // { value: 'magenta-7', label: 'magenta-7' },
      { value: 'magenta-8', label: 'magenta-8' },
      // { value: 'magenta-9', label: 'magenta-9' },
      { value: 'magenta-10', label: 'magenta-10' }
    ]
  },
@@ -631,15 +592,10 @@
    value: 'Gray',
    label: '中性色',
    children: [
      // { value: 'gray-1', label: 'gray-1' },
      { value: 'gray-2', label: 'gray-2' },
      // { value: 'gray-3', label: 'gray-3' },
      { value: 'gray-4', label: 'gray-4' },
      // { value: 'gray-5', label: 'gray-5' },
      { value: 'gray-6', label: 'gray-6' },
      // { value: 'gray-7', label: 'gray-7' },
      { value: 'gray-8', label: 'gray-8' },
      // { value: 'gray-9', label: 'gray-9' },
      { value: 'gray-10', label: 'gray-10' }
    ]
  }
src/utils/utils.js
@@ -765,11 +765,11 @@
          val = val.replace(/(^\s*$)|\t*|\v*/ig, '')
          if (!val && col.required === 'true') { // 必填校验
          if (!val && col.required === 'true') {            // 必填校验
            errors.push(_position + dict['main.excel.content.emptyerror'])
          } else if (val.length > col.limit) {    // 长度校验
          } else if (col.limit && val.length > col.limit) { // 长度校验
            errors.push(_position + dict['main.excel.content.maxlimit'])
          } else {                               // 关键字校验
          } else {                                          // 关键字校验
            keys.forEach(key => {
              let _patten = new RegExp('(^' + key + '\\s+)|(\\s+' + key + '\\s+)', 'ig')
              if (_patten.test(val)) {
src/views/login/index.scss
@@ -41,7 +41,7 @@
        margin-right: 0;
        border-radius: 0;
        text-align: center;
        font-size: 18px;
        font-size: 17px;
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
@@ -52,10 +52,15 @@
    }
  }
  .login-form-1 {
    .ant-tabs-tab {
      text-align: left!important;
      padding-left: 1.6vw!important;
      line-height: 60px!important;
    .ant-tabs.ant-tabs-card {
      .ant-tabs-card-bar {
        .ant-tabs-tab {
          font-size: 18px;
          text-align: left!important;
          padding-left: 1.6vw!important;
          line-height: 60px!important;
        }
      }
    }
  }
  .login-middle {