king
2022-09-06 b8e1395f02c929eaa96b949cf6027ee2a43856a6
src/templates/sharecomponent/actioncomponent/verifyexcelout/index.jsx
@@ -1,19 +1,23 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Tabs, Row, Col, Button, notification, Modal, message, InputNumber, Radio } from 'antd'
import { Form, Tabs, Row, Col, Button, notification, Modal, message, InputNumber, Radio, Spin, Typography, Popconfirm } from 'antd'
import { EditOutlined, StopOutlined, CheckCircleOutlined, SwapOutlined, DeleteOutlined } from '@ant-design/icons'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import SettingUtils from './utils.jsx'
import ColumnForm from './columnform'
import DataSource from './datasource'
import CustomScript from './customscript'
import CodeMirror from '@/templates/zshare/codemirror'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { TabPane } = Tabs
const { confirm } = Modal
const { Paragraph } = Typography
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class VerifyCard extends Component {
@@ -25,6 +29,8 @@
  state = {
    verify: {},
    activeKey: 'setting',
    systemScripts: [],
    defaultscript: '', // 自定义脚本
    excelColumns: [
      {
@@ -33,14 +39,15 @@
        inputType: 'input',
        editable: true,
        unique: true,
        width: '20%'
        width: '17%'
      },
      {
        title: this.props.dict['model.name'],
        dataIndex: 'Text',
        inputType: 'input',
        editable: true,
        width: '20%'
        unique: true,
        width: '17%'
      },
      {
        title: this.props.dict['model.form.columnWidth'],
@@ -49,7 +56,7 @@
        min: 5,
        max: 200,
        editable: true,
        width: '20%',
        width: '12%',
        render: (text) => text || 20
      },
      {
@@ -58,26 +65,29 @@
        inputType: 'select',
        editable: true,
        required: false,
        width: '20%',
        width: '14%',
        render: (text) => {
          if (text === 'image') {
            return '图片'
          } else if (text === 'number') {
            return '数值'
          } else {
            return '文本'
          }
        },
        options: [
          {value: 'text', text: '文本'},
          {value: 'number', text: '数值'},
          {value: 'image', text: '图片'}
        ]
      },
      {
        title: '取绝对值',
        dataIndex: 'abs',
        inputType: 'select',
        inputType: 'radio',
        editable: true,
        required: false,
        width: '20%',
        width: '14%',
        render: (text) => {
          if (text === 'true') {
            return '是'
@@ -89,6 +99,104 @@
          {value: 'true', text: '是'},
          {value: 'false', text: '否'}
        ]
      },
      {
        title: '小数位',
        dataIndex: 'decimal',
        inputType: 'number',
        min: 0,
        max: 18,
        editable: true,
        required: false,
        width: '12%'
      },
      {
        title: '导出',
        dataIndex: 'output',
        inputType: 'radio',
        editable: true,
        required: false,
        width: '14%',
        render: (text) => {
          if (text !== 'false') {
            return '是'
          } else {
            return '否'
          }
        },
        options: [
          {value: 'true', text: '是'},
          {value: 'false', text: '否'}
        ]
      }
    ],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => {
          let title = text.match(/^\s*\/\*.+\*\//)
          title = title && title[0] ? title[0] : ''
          let _text = title ? text.replace(title, '') : text
          return (
            <div>
              {title ? <span style={{color: '#a50'}}>{title}<span style={{fontSize: '12px', marginLeft: '5px'}}>{_text.length}</span></span> : null}
              <Paragraph copyable={{ text: text }} ellipsis={{ rows: 4, expandable: true }}>{_text}</Paragraph>
            </div>
          )
        }
      },
      {
        title: '执行位置',
        dataIndex: 'position',
        width: '10%',
        render: (text, record) => {
          if (record.position === 'init') {
            return <span style={{color: 'orange'}}>初始化</span>
          } else if (record.position === 'front') {
            return <span style={{color: '#26C281'}}>sql前</span>
          } else {
            return <span style={{color: '#1890ff'}}>sql后</span>
          }
        }
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '10%',
        render: (text, record) => record.status === 'false' ?
          (
            <div style={{color: '#ff4d4f'}}>
              禁用
              <StopOutlined style={{marginLeft: '5px'}} />
            </div>
          ) :
          (
            <div style={{color: '#26C281'}}>
              启用
              <CheckCircleOutlined style={{marginLeft: '5px'}}/>
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '140px',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record, 'scripts')} style={{color: '#1890ff'}}><EditOutlined /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record, 'scripts')} style={{color: '#8E44AD'}}><SwapOutlined /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title={this.props.dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record, 'scripts')
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><DeleteOutlined /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
@@ -102,14 +210,17 @@
    }
    _verify.enable = _verify.enable || 'false'
    _verify.dataType = _verify.dataType || 'default'
    _verify.columns = _verify.columns || []
    _verify.scripts = _verify.scripts || []
    if (card.intertype !== 'system') {
      _verify.enable = 'false'
    }
    if (_verify.columns[0] && !_verify.columns[0].type) {
    if (_verify.columns[0] && (!_verify.columns[0].type || !_verify.columns[0].output)) {
      _verify.columns = _verify.columns.map(col => {
        col.type = col.type || 'text'
        col.output = col.output || 'true'
        return col
      })
    }
@@ -124,10 +235,138 @@
      defaultscript = `update ${config.setting.tableName || ''} set idefine5= idefine5+1 ,modifydate=getdate(),cdefine5='已导出',modifyuserid=@userid@ ${search}`
    }
    let search = []
    if (config.setting && config.setting.useMSearch === 'true' && window.GLOB.customMenu) {
      let menu = fromJS(window.GLOB.customMenu).toJS()
      let _search = null
      let filterComponent = (box) => {
        box.components.forEach(item => {
          if (_search) return
          if (item.type === 'search') {
            box.slist = [...box.slist, item.search]
          } else if (item.uuid === config.uuid) {
            _search = box.slist.pop()
          } else if (item.type === 'group') {
            item.components.forEach(m => {
              if (m.uuid !== config.uuid) return
              _search = box.slist.pop()
            })
          } else if (item.type === 'tabs') {
            item.subtabs.forEach(tab => {
              tab.slist = [...box.slist]
              filterComponent(tab)
            })
          }
        })
      }
      menu.slist = []
      filterComponent(menu)
      if (_search) {
        search = _search
      } else {
        menu.components.forEach(item => {
          if (item.type !== 'search') return
          search = item.search
        })
      }
    }
    let searches = fromJS(config.search || []).toJS()
    if (search.length > 0) {
      let keys = searches.map(item => (item.field ? item.field.toLowerCase() : ''))
      search.forEach(item => {
        if (item.field && !keys.includes(item.field.toLowerCase())) {
          searches.push(item)
        }
      })
    }
    this.setState({
      verify: _verify,
      searches: searches,
      activeKey: card.intertype === 'system' && _verify.dataType === 'custom' ? 'setting' : 'columns',
      defaultscript: defaultscript
    })
  }
  componentDidMount () {
    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')
    _sParam.secretkey = Utils.encrypt(_sParam.LText, _sParam.timestamp)
    _sParam.open_key = Utils.encryptOpenKey(_sParam.secretkey, _sParam.timestamp) // 云端数据验证
    Api.getSystemConfig(_sParam).then(res => {
      if (res.status) {
        this.setState({
          systemScripts: res.data.map(item => {
            return {
              name: item.funcname,
              value: window.decodeURIComponent(window.atob(item.longparam))
            }
          })
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  handleEdit = (record, type) => {
    if (type === 'scripts') {
      this.scriptsForm.edit(record)
    }
    let node = document.getElementById('verify-excelout-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, type) => {
    let verify = JSON.parse(JSON.stringify(this.state.verify))
    record.status = record.status === 'false' ? 'true' : 'false'
    if (type === 'scripts') {
      verify.scripts = verify.scripts.map(item => {
        if (item.uuid === record.uuid) {
          return record
        } else {
          return item
        }
      })
    }
    this.setState({
      verify: verify
    })
  }
@@ -192,6 +431,7 @@
    }
    values.uuid = Utils.getuuid()
    values.abs = 'false'
    values.output = 'true'
    verify.columns.push(values)
    this.setState({
@@ -275,7 +515,7 @@
  }
  handleConfirm = () => {
    let verify = fromJS(this.state.verify).toJS()
    const { activeKey, verify } = this.state
    
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
@@ -292,84 +532,168 @@
        return
      }
      if (verify.enable === 'true') {
        this.props.form.validateFieldsAndScroll((err, values) => {
          if (!err) {
            values.sql = values.sql || ''
      if (activeKey === 'backscript' && verify.enable === 'true') {
        this.checkScript(resolve, reject)
      } else if (activeKey === 'setting') {
        this.settingForm.handleConfirm().then(res => {
          let _verify = {...verify, ...res}
          this.setState({
            verify: _verify
          }, () => {
            this.setState({loading: true})
            this.sqlverify(() => { // 验证成功
              this.setState({
                loading: false
              })
              resolve(_verify)
            }, () => {             // 验证失败
              this.setState({
                loading: false
              })
              reject()
            }, verify.scripts)
          })
        })
      } else if (activeKey === 'columns') {
        if (this.columnRef && this.columnRef.state.editingKey) {
          notification.warning({
            top: 92,
            message: '字段未保存!',
            duration: 5
          })
          return
        }
            let _quot = values.sql.match(/'{1}/g)
            let _lparen = values.sql.match(/\({1}/g)
            let _rparen = values.sql.match(/\){1}/g)
        if (this.props.card.intertype !== 'system' || verify.dataType !== 'custom') {
          resolve(verify)
        } else {
          this.setState({loading: true})
    
            _quot = _quot ? _quot.length : 0
            _lparen = _lparen ? _lparen.length : 0
            _rparen = _rparen ? _rparen.length : 0
            if (_quot % 2 !== 0) {
              notification.warning({
                top: 92,
                message: 'sql中\'必须成对出现',
                duration: 5
              })
              return
            } else if (_lparen !== _rparen) {
              notification.warning({
                top: 92,
                message: 'sql中()必须成对出现',
                duration: 5
              })
              return
            } else if (/--/ig.test(values.sql)) {
              notification.warning({
                top: 92,
                message: '自定义sql语句中,不可出现字符 -- ,注释请用 /*内容*/',
                duration: 5
              })
              return
            }
            let error = Utils.verifySql(values.sql, 'customscript')
            if (error) {
              notification.warning({
                top: 92,
                message: 'sql中不可使用' + error,
                duration: 5
              })
              return
            }
            let param = {
              func: 's_debug_sql',
              exec_type: 'y',
              LText: values.sql
            }
            param.LText = param.LText.replace(/@\$|\$@/ig, '')
            param.LText = Utils.formatOptions(param.LText)
            param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
            param.secretkey = Utils.encrypt('', param.timestamp)
            Api.getLocalConfig(param).then(res => {
              if (res.status) {
                resolve({...verify, script: values.sql})
              } else {
                Modal.error({
                  title: res.message
                })
              }
          this.sqlverify(() => { // 验证成功
            this.setState({
              loading: false
            })
            resolve(verify)
          }, () => {             // 验证失败
            this.setState({
              loading: false
            })
            reject()
          }, verify.scripts)
        }
      } else if (activeKey === 'scripts') {
        if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql') && !/^\s+$/.test(this.scriptsForm.props.form.getFieldValue('sql'))) {
          notification.warning({
            top: 92,
            message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
            duration: 5
          })
          return
        }
        this.setState({loading: true})
        this.sqlverify(() => { // 验证成功
          this.setState({
            loading: false
          })
          resolve(verify)
        }, () => {             // 验证失败
          this.setState({
            loading: false
          })
          reject()
        }, verify.scripts)
      } else {
        resolve(verify)
      }
    })
  }
  checkScript = (_resolve, _reject) => {
    this.props.form.validateFieldsAndScroll((err, values) => {
      if (!err) {
        values.sql = values.sql || ''
        let _quot = values.sql.match(/'{1}/g)
        let _lparen = values.sql.match(/\({1}/g)
        let _rparen = values.sql.match(/\){1}/g)
        _quot = _quot ? _quot.length : 0
        _lparen = _lparen ? _lparen.length : 0
        _rparen = _rparen ? _rparen.length : 0
        if (_quot % 2 !== 0) {
          notification.warning({
            top: 92,
            message: 'sql中\'必须成对出现',
            duration: 5
          })
          return
        } else if (_lparen !== _rparen) {
          notification.warning({
            top: 92,
            message: 'sql中()必须成对出现',
            duration: 5
          })
          return
        } else if (/--/ig.test(values.sql)) {
          notification.warning({
            top: 92,
            message: '自定义sql语句中,不可出现字符 -- ,注释请用 /*内容*/',
            duration: 5
          })
          return
        }
        let error = Utils.verifySql(values.sql, 'customscript')
        if (error) {
          notification.warning({
            top: 92,
            message: 'sql中不可使用' + error,
            duration: 5
          })
          return
        }
        let param = {
          func: 's_debug_sql',
          exec_type: 'y',
          LText: values.sql
        }
        param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
        param.LText = param.LText.replace(/@\$|\$@/ig, '').replace(/@(BID|ID|LoginUID|SessionUid|UserID|Appkey|time_id)@/ig, `'${param.timestamp}'`)
        param.LText = Utils.formatOptions(param.LText)
        param.secretkey = Utils.encrypt('', param.timestamp)
        this.setState({
          loading: true
        })
        Api.genericInterface(param).then(res => {
          this.setState({
            loading: false
          })
          if (res.status) {
            let verify = {...this.state.verify, script: values.sql}
            this.setState({verify: verify}, () => {
              _resolve(verify)
            })
          } else {
            notification.warning({
              top: 92,
              message: '自定义脚本不可为空!',
              duration: 5
            Modal.error({
              title: res.message
            })
          }
        })
      } else {
        resolve(verify)
        notification.warning({
          top: 92,
          message: '自定义脚本不可为空!',
          duration: 5
        })
        _reject()
      }
    })
  }
@@ -393,14 +717,22 @@
      if (fields.includes(item.field) || !item.field) return
      fields.push(item.field)
      columns.push({
      let cell = {
        Column: item.field,
        Text: item.label,
        Width: 20,
        abs: 'false',
        output: 'true',
        type: 'text',
        uuid: Utils.getuuid()
      })
      }
      if (item.type === 'number') {
        cell.type = 'number'
        cell.decimal = item.decimal
      }
      columns.push(cell)
    })
    this.setState({
@@ -429,7 +761,7 @@
  changeColumns = (columns) => {
    const { verify } = this.state
    if (columns[0] && (columns[0].type !== 'image' && columns[0].type !== 'text')) {
    if (columns[0] && (columns[0].type !== 'image' && columns[0].type !== 'text' && columns[0].type !== 'number')) {
      columns = columns.map(col => {
        let _cell = {
          uuid: Utils.getuuid(),
@@ -437,6 +769,7 @@
          Text: col.Text,
          Width: 20,
          abs: 'false',
          output: 'true',
          type: 'text',
        }
@@ -447,9 +780,154 @@
    this.setState({verify: {...verify, columns}})
  }
  // 标签切换
  tabchange = (val) => {
    const { card } = this.props
    const { activeKey, verify } = this.state
    if (activeKey === 'backscript' && verify.enable === 'true') {
      this.checkScript(() => {
        this.setState({
          activeKey: val
        })
      }, () => {})
    } else if (card.intertype !== 'system' || verify.dataType !== 'custom') {
      this.setState({activeKey: val})
      return
    } else if (activeKey === 'setting') {
      this.settingForm.handleConfirm().then(res => {
        this.setState({
          verify: {...verify, ...res}
        }, () => {
          this.setState({loading: true})
          this.sqlverify(() => { // 验证成功
            this.setState({
              activeKey: val,
              loading: false
            })
          }, () => {             // 验证失败
            this.setState({
              activeKey: val,
              loading: false
            })
          }, verify.scripts)
        })
      })
    } else if (activeKey === 'columns') {
      if (this.columnRef && this.columnRef.state.editingKey) {
        notification.warning({
          top: 92,
          message: '字段未保存!',
          duration: 5
        })
        return
      }
      this.setState({loading: true})
      this.sqlverify(() => { // 验证成功
        this.setState({
          activeKey: val,
          loading: false
        })
      }, () => {             // 验证失败
        this.setState({
          activeKey: val,
          loading: false
        })
      }, verify.scripts)
    } else if (activeKey === 'scripts') {
      if (this.scriptsForm && this.scriptsForm.props.form.getFieldValue('sql') && !/^\s+$/.test(this.scriptsForm.props.form.getFieldValue('sql'))) {
        notification.warning({
          top: 92,
          message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
          duration: 5
        })
        return
      }
      this.setState({loading: true})
      this.sqlverify(() => { // 验证成功
        this.setState({
          activeKey: val,
          loading: false
        })
      }, () => {             // 验证失败
        this.setState({
          activeKey: val,
          loading: false
        })
      }, verify.scripts)
    } else {
      this.setState({
        activeKey: val
      })
    }
  }
  scriptsChange = (values) => {
    let verify = JSON.parse(JSON.stringify(this.state.verify))
    if (values.uuid) {
      verify.scripts = verify.scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      values.uuid = Utils.getuuid()
      verify.scripts.push(values)
    }
    this.setState({loading: true})
    this.sqlverify(() => { // 验证成功
      this.setState({
        loading: false,
        verify: verify
      })
    }, () => {             // 验证失败
      this.setState({
        loading: false
      })
    }, verify.scripts)
  }
  sqlverify = (_resolve, _reject, scripts) => {
    const { searches, verify } = this.state
    let timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    let sql = SettingUtils.getDebugSql(verify, scripts, searches, Utils, timestamp)
    let param = {
      func: 's_debug_sql',
      exec_type: 'y',
      LText: sql
    }
    param.LText = Utils.formatOptions(param.LText)
    param.timestamp = timestamp
    param.secretkey = Utils.encrypt('', timestamp)
    Api.genericInterface(param).then(result => {
      if (result.status) {
        _resolve()
      } else {
        _reject()
        Modal.error({
          title: result.message
        })
      }
    })
  }
  updateDataType = (val) => {
    this.setState({verify: {...this.state.verify, dataType: val}})
  }
  render() {
    const { card } = this.props
    const { verify, excelColumns, defaultscript } = this.state
    const { verify, excelColumns, defaultscript, scriptsColumns, activeKey, loading } = this.state
    const { getFieldDecorator } = this.props.form
    const formItemLayout = {
      labelCol: {
@@ -464,13 +942,17 @@
    return (
      <div id="verify-excelout-box-tab">
        <Tabs defaultActiveKey="1" className="verify-card-box" onChange={this.tabchange}>
        {loading && <Spin size="large" />}
        <Tabs activeKey={activeKey} className="excelout-verify-card-box" onChange={this.tabchange}>
          {card.intertype === 'system' ? <TabPane tab="基础验证" key="setting">
            <DataSource setting={verify} updateDataType={this.updateDataType} wrappedComponentRef={(inst) => this.settingForm = inst}/>
          </TabPane> : null}
          <TabPane tab={
            <span>
              Excel导出列
              {verify.columns.length ? <span className="count-tip">{verify.columns.length}</span> : null}
            </span>
          } key="1">
          } key="columns">
            <ColumnForm dict={this.props.dict} columnChange={this.columnChange}/>
            <Button className="excel-col-add mk-green" title="添加显示列字段" onClick={this.columnFieldInput}>
              同步显示列
@@ -478,15 +960,32 @@
            <Button className="excel-col-add mk-red" title="清空Excel列" onClick={this.clearField}>
              清空Excel列
            </Button>
            <div style={{color: '#959595', fontSize: '13px', paddingLeft: '10px'}}>如需导出序号,请使用字段 $Index。</div>
            <EditTable actions={['edit', 'move', 'copy', 'del']} type="excelcolumn" data={verify.columns} columns={excelColumns} onChange={this.changeColumns}/>
            <div style={{color: '#959595', fontSize: '13px', paddingLeft: '10px'}}>如需导出序号,请使用字段 $Index;数值类型导出时可进行数据处理(取绝对值、保留小数位)。</div>
            <EditTable actions={['edit', 'move', 'copy', 'del']} type="excelcolumn" wrappedComponentRef={(inst) => this.columnRef = inst} data={verify.columns} columns={excelColumns} onChange={this.changeColumns}/>
          </TabPane>
          {card.intertype === 'system' ? <TabPane tab={
            <span>
              自定义脚本
              {verify.scripts.length ? <span className="count-tip">{verify.scripts.length}</span> : null}
            </span>
          } key="scripts" disabled={verify.dataType !== 'custom'}>
            <CustomScript
              btn={card}
              sheet={verify.tableName}
              usefulfields={verify.columns}
              scripts={verify.scripts}
              systemScripts={this.state.systemScripts}
              scriptsChange={this.scriptsChange}
              wrappedComponentRef={(inst) => this.scriptsForm = inst}
            />
            <EditTable actions={['move']} data={verify.scripts} columns={scriptsColumns} onChange={(scripts) => {this.setState({verify: {...verify, scripts}})}}/>
          </TabPane> : null}
          {card.intertype === 'system' ? <TabPane tab={
            <span>
              回调脚本
              {verify.enable === 'true' ? <span className="count-tip">1</span> : null}
            </span>
          } key="6">
          } key="backscript">
            <Form {...formItemLayout} className="verify-form">
              <Row gutter={24}>
                <Col span={8}>
@@ -514,7 +1013,7 @@
              </Row>
            </Form>
          </TabPane> : null}
          <TabPane tab="信息提示" key="7">
          <TabPane tab="信息提示" key="message">
            <Form {...formItemLayout}>
              <Row gutter={24}>
                <Col offset={6} span={6}>