king
2021-01-31 5ca389a3b7bfc54067547e2b8663ce413f602f3f
2021-01-31
4个文件已修改
9个文件已添加
1485 ■■■■■ 已修改文件
src/menu/sysinterface/index.jsx 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/index.scss 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/baseform/index.jsx 243 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/baseform/index.scss 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/index.jsx 179 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/index.scss 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/simplescript/index.jsx 450 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/simplescript/index.scss 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/settingform/utils.jsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/tabviews/custom/index.jsx 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/sharecomponent/settingcomponent/settingform/utils.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/templates/zshare/editTable/index.jsx 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/menudesign/index.jsx 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/menu/sysinterface/index.jsx
New file
@@ -0,0 +1,215 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { is, fromJS } from 'immutable'
import { Modal, Button, Icon, Popconfirm, message } from 'antd'
import Utils from '@/utils/utils.js'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const SettingForm = asyncComponent(() => import('./settingform'))
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class InterfaceController extends Component {
  static propTpyes = {
    config: PropTypes.object,       // 页面配置
    updateConfig: PropTypes.func    // 更新
  }
  state = {
    visible: false,
    setvisible: false,
    interfaces: [],
    card: null,
    columns: [
      {
        title: '接口名称',
        dataIndex: 'name',
        width: '50%'
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '20%',
        render: (text, record) => record.status !== 'true' ?
          (
            <div>
              禁用
              <Icon style={{marginLeft: '5px'}} type="stop" theme="twoTone" twoToneColor="#ff4d4f" />
            </div>
          ) :
          (
            <div>
              启用
              <Icon style={{marginLeft: '5px'}} type="check-circle" theme="twoTone" twoToneColor="#52c41a" />
            </div>
          )
      },
      {
        title: '操作',
        align: 'center',
        width: '30%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span onClick={() => this.handleEdit(record)} style={{color: '#1890ff', cursor: 'pointer', fontSize: '16px', marginRight: '15px'}}><Icon type="edit" /></span>
            <span onClick={() => {this.copy(record)}} style={{color: '#26C281', cursor: 'pointer', fontSize: '16px', marginRight: '15px'}}><Icon type="copy" /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title="确定删除?"
              onConfirm={() => this.deleteScript(record)
            }>
              <span style={{color: '#ff4d4f', cursor: 'pointer', fontSize: '16px'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  shouldComponentUpdate (nextProps, nextState) {
    return !is(fromJS(this.state), fromJS(nextState))
  }
  copy = (item) => {
    let msg = { key: 'interface', type: 'line', data: item }
    try {
      msg = window.btoa(window.encodeURIComponent(JSON.stringify(msg)))
    } catch {
      console.warn('Stringify Failure')
      msg = ''
    }
    if (msg) {
      let oInput = document.createElement('input')
      oInput.value = msg
      document.body.appendChild(oInput)
      oInput.select()
      document.execCommand('Copy')
      document.body.removeChild(oInput)
      message.success('复制成功。')
    }
  }
  trigger = () => {
    const { config } = this.props
    let interfaces = config.interfaces ? fromJS(config.interfaces).toJS() : []
    this.setState({
      visible: true,
      interfaces
    })
  }
  handleEdit = (record) => {
    this.setState({card: record, setvisible: true})
  }
  deleteScript = (record) => {
    const { config } = this.props
    let interfaces = this.state.interfaces.filter(item => item.uuid !== record.uuid)
    this.setState({ interfaces })
    this.props.updateConfig({...config, interfaces})
  }
  changeScripts = (interfaces) => {
    const { config } = this.props
    this.setState({ interfaces })
    this.props.updateConfig({...config, interfaces})
  }
  settingSave = () => {
    const { config } = this.props
    const { card } = this.state
    let interfaces = fromJS(this.state.interfaces).toJS()
    this.settingRef.handleConfirm().then(res => {
      interfaces = interfaces.map(item => {
        if (item.uuid === card.uuid) {
          res.uuid = item.uuid
          if (res.procMode !== 'inner' && res.preScripts && res.preScripts.filter(item => item.status !== 'false').length === 0) {
            message.warning('未设置前置脚本,不可启用!')
            res.status = 'false'
          } else if (res.callbackType === 'script' && res.cbScripts && res.cbScripts.filter(item => item.status !== 'false').length === 0) {
            message.warning('未设置回调脚本,不可启用!')
            res.status = 'false'
          }
          return res
        }
        return item
      })
      this.setState({
        card: null,
        setvisible: false,
        interfaces
      })
      this.props.updateConfig({...config, interfaces})
    })
  }
  addInterface = () => {
    const { config } = this.props
    let interfaces = fromJS(this.state.interfaces).toJS()
    interfaces.push({
      uuid: Utils.getuuid(),
      name: 'interface ' + (interfaces.length + 1),
      procMode: 'script',
      callbackType: 'script',
      preScripts: [],
      cbScripts: []
    })
    this.setState({
      interfaces
    })
    this.props.updateConfig({...config, interfaces})
  }
  render() {
    const { visible, setvisible, columns, interfaces, card } = this.state
    return (
      <div style={{display: 'inline-block'}}>
        <Button className="mk-border-green" icon="api" onClick={this.trigger}>接口管理</Button>
        <Modal
          title="接口管理"
          wrapClassName="interface-controller-modal"
          visible={visible}
          width={800}
          maskClosable={false}
          onCancel={() => {this.setState({visible: false})}}
          footer={[
            <Button key="colse" onClick={() => {this.setState({visible: false})}}>
              关闭
            </Button>
          ]}
          destroyOnClose
        >
          <Button key="add-interface" className="mk-border-green" onClick={this.addInterface}> 添加 </Button>
          <EditTable key="manage-interface" actions={['move', 'copy']} type="interface" data={interfaces} columns={columns} onChange={this.changeScripts}/>
        </Modal>
        <Modal
          title={card ? card.name : '接口'}
          wrapClassName="interface-edit-modal"
          visible={setvisible}
          width={900}
          maskClosable={false}
          onOk={this.settingSave}
          onCancel={() => { this.setState({ setvisible: false })}}
          destroyOnClose
        >
          <SettingForm config={card} wrappedComponentRef={(inst) => this.settingRef = inst}/>
        </Modal>
      </div>
    )
  }
}
export default InterfaceController
src/menu/sysinterface/index.scss
New file
@@ -0,0 +1,20 @@
.interface-controller-modal {
  >.ant-modal >.ant-modal-content >.ant-modal-body {
    min-height: 400px;
    >.mk-border-green {
      float: right;
      position: relative;
      z-index: 1;
      margin-bottom: 10px;
    }
  }
}
.interface-edit-modal {
  .ant-modal {
    top: 70px;
  }
  .ant-modal-body {
    min-height: 300px;
    padding-top: 5px;
  }
}
src/menu/sysinterface/settingform/baseform/index.jsx
New file
@@ -0,0 +1,243 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Radio, Tooltip, Icon } from 'antd'
import { formRule } from '@/utils/option.js'
import './index.scss'
const { TextArea } = Input
class SettingForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,       // 字典项
    setting: PropTypes.object,    // 数据源配置
    updateStatus: PropTypes.func, // 状态更新
  }
  state = {
    procMode: 'script',
    funcTooltip: '',
    funcRules: []
  }
  UNSAFE_componentWillMount () {
    const { setting } = this.props
    let usefulFields = sessionStorage.getItem('permFuncField')
    if (usefulFields) {
      try {
        usefulFields = JSON.parse(usefulFields)
      } catch {
        usefulFields = []
      }
    } else {
      usefulFields = []
    }
    let tooltip = null
    let rules = []
    if (usefulFields.length > 0) {
      tooltip = '开头可用字符:' + usefulFields.join(', ')
      let str = '^(' + usefulFields.join('|') + ')'
      let _patten = new RegExp(str + formRule.func.innerPattern + '$', 'g')
      rules.push({
        pattern: _patten,
        message: formRule.func.innerMessage
      })
    }
    this.setState({
      procMode: setting.procMode || 'script',
      funcTooltip: tooltip,
      funcRules: rules
    })
  }
  handleConfirm = () => {
    // 表单提交时检查输入值是否正确
    return new Promise((resolve, reject) => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        } else {
          reject(err)
        }
      })
    })
  }
  onRadioChange = (e, key) => {
    let value = e.target.value
    if (key === 'procMode') {
      this.setState({
        procMode: value
      })
    }
    this.props.updateStatus({[key]: value})
  }
  render() {
    const { setting, dict } = this.props
    const { getFieldDecorator } = this.props.form
    const { funcRules, funcTooltip, procMode } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="model-table-datasource-setting-form-box">
        <Form {...formItemLayout} className="model-setting-form">
          <Row gutter={24}>
            <Col span={12}>
              <Form.Item label="接口名">
                {getFieldDecorator('name', {
                  initialValue: setting.name || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '接口名!'
                    },
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="状态">
                {getFieldDecorator('status', {
                  initialValue: setting.status || 'true'
                })(
                <Radio.Group>
                  <Radio value="true">启用</Radio>
                  <Radio value="false">禁用</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="参数处理">
                {getFieldDecorator('procMode', {
                  initialValue: procMode,
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '参数处理方式!'
                    },
                  ]
                })(
                <Radio.Group style={{whiteSpace: 'nowrap'}} onChange={(e) => {this.onRadioChange(e, 'procMode')}}>
                  <Radio value="script">前置脚本</Radio>
                  <Radio value="inner">前置函数</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            {procMode === 'inner' ? <Col span={12}>
              <Form.Item label={
                <Tooltip placement="topLeft" title={funcTooltip}>
                  <Icon type="question-circle" />
                  前置函数
                </Tooltip>
              }>
                {getFieldDecorator('prevFunc', {
                  initialValue: setting.prevFunc || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '前置函数!'
                    },
                    {
                      max: formRule.func.max,
                      message: formRule.func.maxMessage
                    },
                    ...funcRules
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col> : null}
            <Col className="data-source" span={24}>
              <Form.Item label="测试地址">
                {getFieldDecorator('interface', {
                  initialValue: setting.interface || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '测试地址!'
                    },
                  ]
                })(<TextArea rows={2} />)}
              </Form.Item>
            </Col>
            <Col className="data-source" span={24}>
              <Form.Item label={
                <Tooltip placement="topLeft" title="正式系统所使用的的接口地址。">
                  <Icon type="question-circle" />
                  正式地址
                </Tooltip>
              }>
                {getFieldDecorator('proInterface', {
                  initialValue: setting.proInterface || ''
                })(<TextArea rows={2} />)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="请求方式">
                {getFieldDecorator('method', {
                  initialValue: setting.method || 'post',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.select'] + '请求方式!'
                    },
                  ]
                })(
                <Radio.Group>
                  <Radio value="get">GET</Radio>
                  <Radio value="post">POST</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="回调方式">
                {getFieldDecorator('callbackType', {
                  initialValue: setting.callbackType || 'script'
                })(
                <Radio.Group onChange={(e) => {this.onRadioChange(e, 'callbackType')}}>
                  <Radio value="default">默认脚本</Radio>
                  <Radio value="script">自定义脚本</Radio>
                </Radio.Group>)}
              </Form.Item>
            </Col>
            <Col span={12}>
              <Form.Item label="回调表名">
                {getFieldDecorator('cbTable', {
                  initialValue: setting.cbTable || '',
                  rules: [
                    {
                      required: true,
                      message: dict['form.required.input'] + '回调表名!'
                    },
                    {
                      max: formRule.input.max,
                      message: formRule.input.message
                    }
                  ]
                })(<Input placeholder={''} autoComplete="off" />)}
              </Form.Item>
            </Col>
          </Row>
        </Form>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/sysinterface/settingform/baseform/index.scss
New file
@@ -0,0 +1,22 @@
.model-table-datasource-setting-form-box {
  position: relative;
  .model-setting-form {
    .data-source {
      .ant-form-item-label {
        width: 16.5%;
      }
      .ant-form-item-control-wrapper {
        width: 83.5%;
      }
      .CodeMirror {
        height: 150px;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
  }
}
src/menu/sysinterface/settingform/index.jsx
New file
@@ -0,0 +1,179 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, notification, Tabs } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import BaseForm from './baseform'
import zhCN from '@/locales/zh-CN/model.js'
import enUS from '@/locales/en-US/model.js'
import './index.scss'
const { TabPane } = Tabs
const SimpleScript = asyncComponent(() => import('./simplescript'))
class SettingForm extends Component {
  static propTpyes = {
    config: PropTypes.object,       // 页面配置信息
  }
  state = {
    dict: localStorage.getItem('lang') !== 'en-US' ? zhCN : enUS,
    formlist: [],
    btnloading: false,
    activeKey: 'setting',
    setting: null,
    defaultSql: '',
    status: {}
  }
  UNSAFE_componentWillMount() {
    const { config } = this.props
    let _setting = fromJS(config).toJS()
    let _preScripts = _setting.preScripts || []
    let _cbScripts = _setting.cbScripts || []
    this.setState({
      setting: _setting,
      preScripts: _preScripts,
      cbScripts: _cbScripts,
      status: fromJS(_setting).toJS()
    })
  }
  handleConfirm = () => {
    const { activeKey, setting, preScripts, cbScripts } = this.state
    let _loading = false
    if (this.preScriptsForm && this.preScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.cbScriptsForm && this.cbScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    }
    if (_loading) {
      notification.warning({
        top: 92,
        message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
        duration: 5
      })
      return Promise.reject()
    }
    // 表单提交时检查输入值是否正确
    if (activeKey === 'setting') {
      return new Promise((resolve, reject) => {
        this.settingForm.handleConfirm().then(res => {
          resolve({...res, preScripts, cbScripts})
        }, () => {
          reject()
        })
      })
    } else {
      return new Promise((resolve) => {
        resolve({...setting, preScripts, cbScripts})
      })
    }
  }
  // 标签切换
  changeTab = (val) => {
    const { activeKey } = this.state
    let _loading = false
    if (this.preScriptsForm && this.preScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    } else if (this.cbScriptsForm && this.cbScriptsForm.props.form.getFieldValue('sql')) {
      _loading = true
    }
    if (_loading) {
      notification.warning({
        top: 92,
        message: '存在未保存脚本,请点击确定保存,或点击取消放弃修改!',
        duration: 5
      })
      return
    }
    if (activeKey === 'setting') {
      this.settingForm.handleConfirm().then(res => {
        this.setState({
          setting: res,
          activeKey: val
        })
      })
    } else {
      this.setState({
        activeKey: val
      })
    }
  }
  // 前置脚本更新
  preScriptsUpdate = (preScripts) => {
    this.setState({preScripts})
  }
  // 后置脚本更新
  cbScriptsUpdate = (cbScripts) => {
    this.setState({cbScripts})
  }
  updateStatus = (status) => {
    this.setState({status: {...this.state.status, ...status}})
  }
  render() {
    const { dict, activeKey, setting, preScripts, cbScripts, status } = this.state
    return (
      <div className="model-interface-form-box" id="model-interface-form-body">
        <Tabs activeKey={activeKey} className="verify-card-box" onChange={this.changeTab}>
          <TabPane tab="数据源" key="setting">
            <BaseForm
              dict={dict}
              setting={setting}
              updateStatus={this.updateStatus}
              wrappedComponentRef={(inst) => this.settingForm = inst}
            />
          </TabPane>
          <TabPane tab={
            <span>
              前置脚本
              {preScripts.length ? <span className="count-tip">{preScripts.length}</span> : null}
            </span>
          } disabled={status.procMode !== 'script'} key="prescripts">
            <SimpleScript
              dict={dict}
              type="front"
              setting={setting}
              scripts={preScripts}
              scriptsUpdate={this.preScriptsUpdate}
              wrappedComponentRef={(inst) => this.preScriptsForm = inst}
            />
          </TabPane>
          <TabPane tab={
            <span>
              回调脚本
              {cbScripts.length ? <span className="count-tip">{cbScripts.length}</span> : null}
            </span>
          } disabled={status.callbackType !== 'script'} key="cbscripts">
            <SimpleScript
              dict={dict}
              type="back"
              setting={setting}
              scripts={cbScripts}
              scriptsUpdate={this.cbScriptsUpdate}
              wrappedComponentRef={(inst) => this.cbScriptsForm = inst}
            />
          </TabPane>
        </Tabs>
      </div>
    )
  }
}
export default Form.create()(SettingForm)
src/menu/sysinterface/settingform/index.scss
New file
@@ -0,0 +1,65 @@
.model-interface-form-box {
  position: relative;
  >.ant-spin {
    position: absolute;
    top: 150px;
    left: calc(50% - 16px);
  }
  .count-tip {
    position: absolute;
    top: 0px;
    color: #1890ff;
    font-size: 12px;
  }
  .model-table-setting-form {
    .textarea {
      .ant-form-item-label {
        width: 16.3%;
      }
      .ant-form-item-control-wrapper {
        width: 83.33333333%;
      }
    }
    .anticon-question-circle {
      color: #c49f47;
      margin-right: 3px;
    }
    .text-area {
      .CodeMirror {
        height: 150px;
      }
    }
  }
  .operation-btn {
    display: inline-block;
    font-size: 16px;
    padding: 0 5px;
    cursor: pointer;
  }
  td {
    word-break: break-all;
  }
  .setting-custom-back {
    position: absolute;
    top: -20px;
    left: -10px;
    font-size: 16px;
    z-index: 1;
    cursor: pointer;
    padding: 10px;
    color: rgb(24, 144, 255);
  }
  .to-custom-script {
    float: right;
    color: #1890ff;
    margin-right: 12px;
    margin-top: 15px;
    cursor: pointer;
    border: 0;
    box-shadow: unset;
  }
  .ant-tabs-nav-wrap {
    text-align: center;
  }
}
src/menu/sysinterface/settingform/simplescript/index.jsx
New file
@@ -0,0 +1,450 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { fromJS } from 'immutable'
import { Form, Row, Col, Icon, Button, notification, Select, Popconfirm, Typography, Modal, Radio } from 'antd'
import moment from 'moment'
import Utils from '@/utils/utils.js'
import Api from '@/api'
import SettingUtils from '../utils'
import CodeMirror from '@/templates/zshare/codemirror'
import asyncComponent from '@/utils/asyncComponent'
import './index.scss'
const { Paragraph } = Typography
const EditTable = asyncComponent(() => import('@/templates/zshare/editTable'))
class CustomForm extends Component {
  static propTpyes = {
    dict: PropTypes.object,         // 字典项
    type: PropTypes.string,         // 脚本类型
    setting: PropTypes.object,      // 设置
    scripts: PropTypes.array,       // 自定义脚本列表
    scriptsChange: PropTypes.func,  // 自定义脚本切换时验证
    scriptsUpdate: PropTypes.func   // 表单
  }
  state = {
    editItem: null,
    loading: false,
    systemScripts: [],
    scriptsColumns: [
      {
        title: 'SQL',
        dataIndex: 'sql',
        width: '60%',
        render: (text) => {
          let title = text.match(/^\s*\/\*.+\*\//)
          title = title && title[0] ? title[0] : ''
          text = title ? text.replace(title, '') : text
          return (
            <div>
              {title ? <span style={{color: '#a50'}}>{title}</span> : null}
              <Paragraph copyable ellipsis={{ rows: 4, expandable: true }}>{text}</Paragraph>
            </div>
          )
        }
      },
      {
        title: '执行位置',
        dataIndex: 'position',
        width: '13%',
        render: (text, record) => {
          if (record.position === 'front') {
            return 'sql前'
          } else {
            return 'sql后'
          }
        }
      },
      {
        title: '状态',
        dataIndex: 'status',
        width: '12%',
        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: '15%',
        dataIndex: 'operation',
        render: (text, record) =>
          (<div style={{textAlign: 'center'}}>
            <span className="operation-btn" title={this.props.dict['model.edit']} onClick={() => this.handleEdit(record)} style={{color: '#1890ff'}}><Icon type="edit" /></span>
            <span className="operation-btn" title={this.props.dict['header.form.status.change']} onClick={() => this.handleStatus(record)} style={{color: '#8E44AD'}}><Icon type="swap" /></span>
            <Popconfirm
              overlayClassName="popover-confirm"
              title={this.props.dict['model.query.delete']}
              onConfirm={() => this.handleDelete(record)
            }>
              <span className="operation-btn" style={{color: '#ff4d4f'}}><Icon type="delete" /></span>
            </Popconfirm>
          </div>)
      }
    ]
  }
  UNSAFE_componentWillMount() {
    const { scripts } = this.props
    let scriptsColumns = fromJS(this.state.scriptsColumns).toJS()
    this.setState({
      scripts: fromJS(scripts).toJS(),
      scriptsColumns
    })
  }
  componentDidMount () {
    this.getsysScript()
  }
  getsysScript = () => {
    let _scriptSql = `Select distinct func+Remark as funcname,longparam, s.Sort from  s_custom_script s inner join (select OpenID from sapp where ID=@Appkey@) p on s.openid = case when s.appkey='' then s.openid else p.OpenID end order by s.Sort`
    _scriptSql = Utils.formatOptions(_scriptSql)
    let _sParam = {
      func: 'sPC_Get_SelectedList',
      LText: _scriptSql,
      obj_name: 'data',
      arr_field: 'funcname,longparam'
    }
    _sParam.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    _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) {
        let _scripts = res.data.map(item => {
          let _item = {
            name: item.funcname,
            value: window.decodeURIComponent(window.atob(item.longparam))
          }
          return _item
        })
        this.setState({
          systemScripts: _scripts
        })
      } else {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
      }
    })
  }
  handleCancel = () => {
    this.setState({
      editItem: null
    })
    this.props.form.setFieldsValue({
      sql: ''
    })
  }
  handleConfirm = () => {
    const { scripts, editItem } = this.state
    let _sql = this.props.form.getFieldValue('sql')
    if (!_sql) {
      notification.warning({
        top: 92,
        message: '请填写自定义脚本!',
        duration: 5
      })
      return
    } else if (/^\s+$/.test(_sql)) {
      notification.warning({
        top: 92,
        message: '自定义脚本不可为空!',
        duration: 5
      })
      return
    }
    let values = {
      uuid: editItem && editItem.uuid ? editItem.uuid : Utils.getuuid(),
      sql: _sql,
    }
    if (this.props.form.getFieldValue('position')) {
      values.position = this.props.form.getFieldValue('position')
    }
    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 _scripts = fromJS(scripts).toJS()
    if (editItem && editItem.uuid) {
      _scripts = _scripts.map(item => {
        if (item.uuid === values.uuid) {
          return values
        } else {
          return item
        }
      })
    } else {
      _scripts.push(values)
    }
    let param = {
      func: 's_debug_sql',
      exec_type: 'y',
      LText: SettingUtils.getCustomDebugSql(_scripts)
    }
    param.LText = Utils.formatOptions(param.LText)
    param.timestamp = moment().format('YYYY-MM-DD HH:mm:ss')
    param.secretkey = Utils.encrypt('', param.timestamp)
    this.setState({loading: true})
    Api.getLocalConfig(param).then(result => {
      if (result.status) {
        this.setState({
          loading: false,
          scripts: _scripts,
          editItem: null
        })
        this.props.scriptsUpdate(_scripts)
        this.props.form.setFieldsValue({
          sql: ''
        })
      } else {
        this.setState({loading: false})
        Modal.error({
          title: result.message
        })
      }
    })
  }
  selectScript = (value, option) => {
    if (!value || !option) return
    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
    })
  }
  handleEdit = (record) => {
    const { type } = this.props
    this.setState({
      editItem: record
    })
    if (type === 'front') {
      this.props.form.setFieldsValue({
        sql: record.sql
      })
    } else {
      this.props.form.setFieldsValue({
        sql: record.sql,
        position: record.position || 'back'
      })
    }
    this.scrolltop()
  }
  scrolltop = () => {
    let node = document.getElementById('model-interface-form-body').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)
    }
  }
  changeScripts = (scripts) => {
    this.setState({scripts})
    this.props.scriptsUpdate(scripts)
  }
  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})
    this.props.scriptsUpdate(scripts)
  }
  handleDelete = (record) => {
    let scripts = fromJS(this.state.scripts).toJS()
    scripts = scripts.filter(item => item.uuid !== record.uuid)
    this.setState({ scripts })
    this.props.scriptsUpdate(scripts)
  }
  render() {
    const { setting, scripts, type } = this.props
    const { getFieldDecorator } = this.props.form
    const { scriptsColumns, systemScripts } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <div className="modal-menu-setting-script">
        <Form {...formItemLayout}>
          <Row gutter={24}>
            <Col span={8}>
              <Form.Item label={'回调表名'} style={{whiteSpace: 'nowrap', margin: 0}}>
                {setting.cbTable}
              </Form.Item>
            </Col>
            <Col span={16}>
              <Form.Item label={'报错字段'} style={{margin: 0}}>
                ErrorCode, retmsg
              </Form.Item>
            </Col>
            <Col span={24} className="sqlfield">
              <Form.Item label={'可用字段'}>
                bid, loginuid, sessionuid, userid, username, fullname, appkey, time_id
              </Form.Item>
            </Col>
            {type === 'back' ? <Col span={8} style={{whiteSpace: 'nowrap'}}>
              <Form.Item style={{marginBottom: 0}} label="执行位置">
                {getFieldDecorator('position', {
                  initialValue: 'front'
                })(
                  <Radio.Group>
                    <Radio value="front">sql前</Radio>
                    <Radio value="back">sql后</Radio>
                  </Radio.Group>
                )}
              </Form.Item>
            </Col> : null}
            <Col span={10} className="quick-add">
              <Form.Item label={'快捷添加'} style={{marginBottom: 0}}>
                <Select
                  allowClear
                  showSearch
                  filterOption={(input, option) => option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0}
                  onChange={this.selectScript}
                >
                  {type === 'back' ? <Select.Option key="default" value={`declare @${setting.cbTable} table (mk_api_key nvarchar(100),mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))\n/*@${setting.cbTable}_data table (mk_level nvarchar(10),mk_id nvarchar(50),mk_bid nvarchar(50))*/`}>默认sql</Select.Option> : null}
                  {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: ''
                })(<CodeMirror />)}
              </Form.Item>
            </Col>
          </Row>
        </Form>
        <EditTable data={scripts} actions={['move']} columns={scriptsColumns} onChange={this.changeScripts}/>
      </div>
    )
  }
}
export default Form.create()(CustomForm)
src/menu/sysinterface/settingform/simplescript/index.scss
New file
@@ -0,0 +1,45 @@
.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%;
    }
  }
  .quick-add {
    .ant-col-sm-8 {
      width: 26%;
    }
    .ant-col-sm-16 {
      width: 74%;
    }
  }
  .sql {
    .ant-col-sm-8 {
      width: 10.5%;
    }
    .ant-col-sm-16 {
      width: 89.5%;
      padding-top: 4px;
    }
    .CodeMirror {
      height: 350px;
    }
  }
  div.ant-typography {
    margin-bottom: 0;
  }
}
src/menu/sysinterface/settingform/utils.jsx
New file
@@ -0,0 +1,45 @@
export default class SettingUtils {
  /**
   * @description 生成前置或后置语句
   * @return {String}  scripts       脚本
   */
  static getCustomDebugSql (scripts) {
    let sql = ''
    let _customScript = ''
    scripts.forEach(script => {
      if (script.status === 'false') return
      _customScript += `
      ${script.sql}
      `
    })
    if (_customScript) {
      _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
    _customScript = _customScript.replace(/@\$|\$@/ig, '')
    _customScript = _customScript.replace(/@userName@|@fullName@/ig, `''`)
    // 外联数据库替换
    if (window.GLOB.externalDatabase !== null) {
      _customScript = _customScript.replace(/@db@/ig, window.GLOB.externalDatabase)
    }
    if (_customScript) {
      sql = `/* sql 验证 */
        ${_customScript}
        aaa:
        if @ErrorCode!=''
          insert into tmp_err_retmsg (ID, ErrorCode, retmsg, CreateUserID) select @time_id@,@ErrorCode, @retmsg,@UserID@
      `
    }
    sql = sql.replace(/\n\s{8}/ig, '\n')
    console.info(sql)
    return sql
  }
}
src/tabviews/custom/index.jsx
@@ -11,6 +11,7 @@
import zhCN from '@/locales/zh-CN/main.js'
import enUS from '@/locales/en-US/main.js'
import Utils from '@/utils/utils.js'
import UtilsDM from '@/utils/utils-datamanage.js'
import asyncComponent from '@/utils/asyncComponent'
import MKEmitter from '@/utils/events.js'
import NotFount from '@/components/404'
@@ -180,6 +181,8 @@
        if (!this.props.Tab) {
          this.setShortcut()
        }
        this.loadData()
      })
    } else {
      this.setState({
@@ -229,6 +232,178 @@
    }
  }
  loadData = () => {
    const { config } = this.state
    if (!config.interfaces || config.interfaces.length === 0) return
    let inters = []
    config.interfaces.forEach(item => {
      if (item.status !== 'true') return
      if (window.GLOB.systemType === 'production' && !item.proInterface) {
        notification.warning({
          top: 92,
          message: `《${item.name}》未设置正式系统地址!`,
          duration: 3
        })
        return
      }
      inters.push(item)
    })
    if (inters.length > 0) {
      this.loadOutResource(inters)
    }
  }
  loadOutResource = (params) => {
    let setting = params.shift()
    let param = UtilsDM.getPrevQueryParams(setting, [], this.state.BID, this.props.menuType)
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (res.mk_ex_invoke === 'false') {
          if (params.length > 0) {
            this.loadOutResource(params)
          }
        } else {
          this.customOuterRequest(res, setting, params)
        }
      } else {
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    })
  }
  customOuterRequest = (result, setting, params) => {
    let url = ''
    if (window.GLOB.systemType === 'production') {
      url = setting.proInterface
    } else {
      url = setting.interface
    }
    let mkey = result.mk_api_key || ''
    delete result.mk_ex_invoke
    delete result.status
    delete result.message
    delete result.ErrCode
    delete result.ErrMesg
    delete result.mk_api_key
    let param = {}
    Object.keys(result).forEach(key => {
      key = key.replace(/^mk_/ig, '')
      param[key] = result[key]
    })
    Api.directRequest(url, setting.method, param).then(res => {
      if (typeof(res) !== 'object' || Array.isArray(res)) {
        let error = '未知的返回结果!'
        if (typeof(res) === 'string') {
          error = res.replace(/'/ig, '"')
        }
        let _result = {
          mk_api_key: mkey,
          $ErrCode: 'E',
          $ErrMesg: error
        }
        this.customCallbackRequest(_result, setting, params)
      } else {
        res.mk_api_key = mkey
        this.customCallbackRequest(res, setting, params)
      }
    }, (e) => {
      let _result = {
        mk_api_key: mkey,
        $ErrCode: 'E',
        $ErrMesg: e && e.statusText ? e.statusText : ''
      }
      this.customCallbackRequest(_result, setting, params)
    })
  }
  customCallbackRequest = (result, setting, params) => {
    let errSql = ''
    if (result.$ErrCode === 'E') {
      errSql = `
        set @ErrorCode='E'
        set @retmsg='${result.$ErrMesg}'
      `
      delete result.$ErrCode
      delete result.$ErrMesg
    }
    let lines = UtilsDM.getCallBackSql(setting, result)
    let param = {}
    if (setting.callbackType === 'script') { // 使用自定义脚本
      let sql = lines.map(item => (`
        ${item.insert}
        ${item.selects.join(` union all
        `)}
      `))
      sql = sql.join('')
      param = UtilsDM.getCallBackQueryParams(setting, sql, errSql)
      if (this.state.BID) {
        param.BID = this.state.BID
      }
      if (this.props.menuType === 'HS') { // 函数 sPC_TableData_InUpDe 云端验证
        param.open_key = Utils.encryptOpenKey(param.secretkey, param.timestamp)
      }
    } else {
      param.func = 's_ex_result_back'
      param.s_ex_result = lines.map((item, index) => ({
        MenuID: this.props.MenuID,
        MenuName: this.props.MenuName,
        TableName: item.table,
        LongText: window.btoa(window.encodeURIComponent(`${item.insert}  ${item.selects.join(` union all `)}`)),
        Sort: index + 1
      }))
      if ((window.GLOB.systemType !== 'production' && options.sysType !== 'cloud') || window.debugger === true) {
        let sql = lines.map(item => (`
          ${item.insert}
          ${item.selects.join(` union all
          `)}
        `))
        sql = sql.join('')
        console.info(sql.replace(/\n\s{10}/ig, '\n'))
      }
    }
    Api.genericInterface(param).then(res => {
      if (res.status) {
        if (params.length > 0) {
          this.loadOutResource(params)
        }
      } else {
        notification.error({
          top: 92,
          message: res.message,
          duration: 10
        })
      }
    })
  }
  filterComponent = (components, roleId, permAction, permMenus) => {
    return components.filter(item => {
      if (item.type === 'tabs') {
src/templates/sharecomponent/settingcomponent/settingform/utils.jsx
@@ -117,12 +117,8 @@
      `
    })
    if (_customScript && regoptions) {
    if (_customScript) {
      _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000),@UserName nvarchar(50),@FullName nvarchar(50) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    } else if (_customScript) {
      _customScript = `declare @ErrorCode nvarchar(50),@retmsg nvarchar(4000) select @ErrorCode='',@retmsg =''
        ${_customScript}
      `
    }
src/templates/zshare/editTable/index.jsx
@@ -172,7 +172,15 @@
    let columns = fromJS(this.props.columns).toJS()
    if (actions && (actions.includes('edit') || actions.includes('copy') || actions.includes('del'))) {
      columns.push({
      let _operation = null
      columns = columns.filter(item => {
        if (item.dataIndex === 'operation') {
          _operation = item
        }
        return item.dataIndex !== 'operation'
      })
      let operation = {
        title: (<div>
          {eTDict['model.operation']}
          {actions.includes('copy') ? (
@@ -213,7 +221,13 @@
            </div>
          )
        }
      })
      }
      if (_operation) {
        operation.render = _operation.render
        operation.width = _operation.width
      }
      columns.push(operation)
    }
    this.setState({
src/views/menudesign/index.jsx
@@ -36,6 +36,7 @@
const PasteController = asyncComponent(() => import('@/menu/pastecontroller'))
const PaddingController = asyncComponent(() => import('@/menu/padcontroller'))
const StyleController = asyncComponent(() => import('@/menu/stylecontroller'))
const SysInterface = asyncComponent(() => import('@/menu/sysinterface'))
const PictureController = asyncComponent(() => import('@/menu/picturecontroller'))
const ModalController = asyncComponent(() => import('@/menu/modalconfig/controller'))
const StyleCombController = asyncComponent(() => import('@/menu/stylecombcontroller'))
@@ -953,6 +954,7 @@
                  <div> {config && config.MenuName} </div>
                } bordered={false} extra={
                  <div>
                    <SysInterface config={config} updateConfig={this.updateConfig}/>
                    <PictureController/>
                    <StyleCombControlButton menu={config} />
                    <PasteController type="menu" Tab={null} insert={this.insert} />