king
2021-07-18 2850799963a5bff04aeeb9eb73dc4eb91a0dc165
2021-07-18
5个文件已修改
15个文件已添加
2264 ■■■■■ 已修改文件
src/router/index.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/appmanage/index.jsx 332 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/appmanage/index.scss 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/appmanage/scriptform/index.jsx 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/appmanage/scriptform/index.scss 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/design/header/index.jsx 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/design/header/index.scss 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/api/index.js 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/header/index.jsx 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/header/index.scss 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/history/index.jsx 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/history/index.scss 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/index.jsx 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/index.scss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/workspace/editTable/index.jsx 255 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/workspace/editTable/index.scss 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/workspace/index.jsx 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/workspace/index.scss 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/workspace/request/index.jsx 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/interface/workspace/request/index.scss 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js
@@ -18,6 +18,7 @@
const MenuDesign = asyncLoadComponent(() => import('@/views/menudesign'))
const BillPrint = asyncLoadComponent(() => import('@/views/billprint'))
const PrintT = asyncLoadComponent(() => import('@/views/printTemplate'))
const Interface = asyncLoadComponent(() => import('@/views/interface'))
const routers = [
  {path: '/login', name: 'login', component: Login, auth: false},
@@ -31,7 +32,8 @@
  {path: '/mobdesign/:param', name: 'mobdesign', component: MobDesign, auth: true},
  {path: '/menudesign/:param', name: 'menudesign', component: MenuDesign, auth: true},
  {path: '/billprint/:param', name: 'billprint', component: BillPrint, auth: true},
  {path: '/paramsmain/:param', name: 'pmain', component: Main, auth: true}
  {path: '/paramsmain/:param', name: 'pmain', component: Main, auth: true},
  {path: '/interface', name: 'interface', component: Interface, auth: true}
]
export default class RouteConfig extends Component {
@@ -105,15 +107,11 @@
    return (
      <HashRouter>
        <Switch>
          {
            routers.map((item, index) => {
          {routers.map((item, index) => {
              return (
                <Route key={index} path={item.path} name={item.name} exact render={ props => {
                  return this.controlRoute(item, props)
                }}/>
              <Route key={index} path={item.path} name={item.name} exact render={ props => this.controlRoute(item, props)}/>
              )
            })
          }
          })}
          <Redirect exact from="/" to="login"/>
          <Route component= {NotFound}/>
        </Switch>
src/views/appmanage/index.jsx
@@ -19,6 +19,7 @@
const Header = asyncComponent(() => import('@/mob/header'))
const MutilForm = asyncComponent(() => import('./mutilform'))
const TransForm = asyncComponent(() => import('./transform'))
const ScriptForm = asyncComponent(() => import('./scriptform'))
const SubMutilForm = asyncComponent(() => import('./submutilform'))
let base_url = ''
@@ -81,9 +82,10 @@
        title: '操作',
        key: 'action',
        align: 'center',
        width: '230px',
        render: (text, record) => (
          <div>
            <Button type="link" onClick={() => this.setState({ selectTran: record, transVisible: 'edit' })} style={{color: '#8E44AD'}}>修改</Button>
          <div onClick={() => this.forbid = true}>
            <Button type="link" onClick={() => this.setState({ editTran: record, transVisible: 'edit' })} style={{color: '#8E44AD'}}>修改</Button>
            <Button type="link" onClick={() => this.deleteTran(record)} style={{color: '#ff4d4f'}}>删除</Button>
            <Button type="link" onClick={() => this.enableTran(record)} style={{color: '#26C281'}}>启用</Button>
          </div>
@@ -94,9 +96,23 @@
    translist: [],
    tranSearchKey: '',
    selectTran: null,
    editTran: null,
    transIndex: 1,
    transTotal: 0
    transTotal: 0,
    scriptcolumns: [
      { title: '关键字', dataIndex: 'KeyWords', key: 'KeyWords', align: 'left' },
      { title: '描述', dataIndex: 'Remark', key: 'Remark', align: 'left' },
      { title: '类型', dataIndex: 'TypeName', key: 'TypeName', align: 'left' },
      { title: '排序', dataIndex: 'Sort', key: 'Sort', align: 'left' },
    ],
    scriptVisible: false,
    scriptlist: [],
    scriptSearchKey: '',
    scriptIndex: 1,
    scriptTotal: 0,
  }
  forbid = false
  UNSAFE_componentWillMount() {
    document.body.className = ''
@@ -138,7 +154,10 @@
      if (result.status) {
        this.setState({
          loading: false,
          translist: result.data
          translist: result.data,
          selectTran: null,
          scriptlist: [],
          transTotal: result.total
        })
      } else {
        this.setState({
@@ -153,6 +172,72 @@
    })
  }
  getScriptList = () => {
    const { scriptSearchKey, scriptIndex, selectTran } = this.state
    if (!selectTran || !selectTran.ID) {
      notification.warning({
        top: 92,
        message: '缺少传输号ID!',
        duration: 3
      })
      return
    }
    let param = {
      func: 's_get_sVersionDetail',
      dataM: 'Y',
      PageSize: 10,
      PageIndex: scriptIndex,
      OrderCol: 'Sort desc',
      BID: selectTran.ID,
    }
    if (scriptSearchKey) {
      param.TypeName = scriptSearchKey
      param.KeyWords = scriptSearchKey
      param.Remark = scriptSearchKey
    }
    this.setState({
      loading: true
    })
    Api.getCloudConfig(param).then(result => {
      if (result.status) {
        this.setState({
          loading: false,
          scriptlist: result.data,
          scriptTotal: result.total,
          selectScriptKeys: []
        })
      } else {
        this.setState({
          loading: false
        })
        notification.warning({
          top: 92,
          message: result.message,
          duration: 5
        })
      }
    })
  }
  scriptSearch = (value) => {
    this.setState({scriptSearchKey: value, scriptIndex: 1}, () => {
      this.getScriptList()
    })
  }
  changeScriptTable = (pagination) => {
    this.setState({
      scriptIndex: pagination.current
    }, () => {
      this.getScriptList()
    })
  }
  changeTable = (pagination) => {
    this.setState({
      transIndex: pagination.current
@@ -162,13 +247,13 @@
  }
  tranSearch = (value) => {
    this.setState({tranSearchKey: value}, () => {
    this.setState({tranSearchKey: value, transIndex: 1}, () => {
      this.getTransList()
    })
  }
  submitTrans = () => {
    const { transVisible, selectTran } = this.state
    const { transVisible, editTran } = this.state
    this.transRef.handleConfirm().then(res => {
      this.setState({
@@ -184,7 +269,7 @@
      } else {
        param.func = 's_sVersion_upt'
        param.ProgramName = res.ProgramName
        param.ID = selectTran.ID
        param.ID = editTran.ID
      }
      Api.getCloudConfig(param).then(result => {
@@ -217,6 +302,49 @@
    })
  }
  submitScript = () => {
    // const { selectTran } = this.state
    this.scriptRef.handleConfirm().then(res => {
      this.setState({
        confirmloading: true
      })
      // let param = {}
      // s_sVersionDetail_Add
      // s_sVersionDetail_CloudAdd
      // Api.getCloudConfig(param).then(result => {
      //   if (result.status) {
      //     notification.success({
      //       top: 92,
      //       message: '操作成功!',
      //       duration: 3
      //     })
      //     this.setState({
      //       scriptIndex: 1,
      //       confirmloading: false,
      //       scriptVisible: false
      //     }, () => {
      //       this.getScriptList()
      //     })
      //   } else {
      //     this.setState({
      //       confirmloading: false
      //     })
      //     notification.warning({
      //       top: 92,
      //       message: result.message,
      //       duration: 5
      //     })
      //   }
      // }, () => {
      //   this.setState({
      //     confirmloading: false
      //   })
      // })
    })
  }
  deleteTran = (record) => {
    const _this = this
@@ -235,10 +363,6 @@
                top: 92,
                message: '操作成功!',
                duration: 3
              })
              _this.setState({
                selectTran: null,
              })
              _this.getTransList()
            } else {
@@ -276,10 +400,6 @@
                top: 92,
                message: '操作成功!',
                duration: 3
              })
              _this.setState({
                selectTran: record,
              })
              _this.getTransList()
            } else {
@@ -322,6 +442,10 @@
          return item
        })
        if (!selectApp && applist[0]) {
          selectApp = applist[0]
        }
        
        this.setState({
          loading: false,
@@ -495,11 +619,113 @@
    this.setState({ selectApp })
  }
  onTransChange = selectedRowKeys => {
    const { translist } = this.state
    let selectTran = translist.filter(item => item.ID === selectedRowKeys[0])[0]
  onScriptChange = selectedRowKeys => {
    this.setState({ selectScriptKeys: selectedRowKeys })
  }
    this.setState({ selectTran })
  onScriptSelect = (record) => {
    const { selectScriptKeys } = this.state
    if (selectScriptKeys.includes(record.ID)) {
      this.setState({ selectScriptKeys: selectScriptKeys.filter(key => key !== record.ID) })
    } else {
      this.setState({ selectScriptKeys: [...selectScriptKeys, record.ID]})
    }
  }
  deleteScripts = () => {
    const { selectScriptKeys, selectTran } = this.state
    if (selectScriptKeys.length === 0) {
      notification.warning({
        top: 92,
        message: '请选择要删除的脚本!',
        duration: 3
      })
      return
    }
    let params = selectScriptKeys.map(key => {
      return {
        func: 's_sVersionDetail_del',
        BID: selectTran.ID,
        ID: key
      }
    })
    const _this = this
    confirm({
      content: '确定要执行吗?',
      onOk() {
        return new Promise(resolve => {
          let deffers = params.map((param, i) => {
            return new Promise(resolve => {
              setTimeout(() => {
                Api.getCloudConfig(param).then(res => {
                  resolve(res)
                }, () => {
                  resolve({status: false, message: '删除失败!'})
                })
              }, i * 150)
            })
          })
          Promise.all(deffers).then(result => {
            let errorMsg = ''
            result.forEach(res => {
              if (!res.status) {
                errorMsg = res.message
              }
            })
            if (errorMsg) {
              notification.warning({
                top: 92,
                message: errorMsg,
                duration: 3
              })
            } else {
              notification.success({
                top: 92,
                message: '执行成功。',
                duration: 3
              })
              _this.setState({
                scriptIndex: 1
              }, () => {
                _this.getScriptList()
              })
            }
            resolve()
          })
        })
      },
      onCancel() {}
    })
  }
  onTransChange = selectedRowKeys => {
    const { translist, selectTran } = this.state
    let _selectTran = translist.filter(item => item.ID === selectedRowKeys[0])[0]
    this.setState({ selectTran: _selectTran })
    if (!selectTran || selectTran.ID !== _selectTran.ID) {
      this.setState({ scriptIndex: 1 }, () => {
        this.getScriptList()
      })
    }
  }
  onTransSelect = (record) => {
    const { selectTran } = this.state
    this.setState({ selectTran: record })
    if (!selectTran || selectTran.ID !== record.ID) {
      this.setState({ scriptIndex: 1 }, () => {
        this.getScriptList()
      })
    }
  }
  /**
@@ -661,7 +887,7 @@
  }
  render () {
    const { loading, visible, subVisible, columns, transcolumns, applist, translist, transVisible, selectApp, selectTran, selectSubApp } = this.state
    const { loading, visible, subVisible, columns, transcolumns, applist, translist, transVisible, selectApp, selectTran, selectSubApp, scriptVisible, scriptlist, scriptcolumns, selectScriptKeys } = this.state
    return (
      <div className="mk-app-manage">
@@ -669,8 +895,7 @@
          <Header view="manage" />
          {loading ?
            <div className="loading-mask">
              <div className="ant-spin-blur"></div>
              <Spin />
              <Spin size="large" />
            </div> : null
          }
          <div className="view-wrap">
@@ -700,32 +925,41 @@
                  pagination={{
                    current: this.state.transIndex,
                    pageSize: 10,
                    total: this.state.transTotal || 0
                    total: this.state.transTotal || 0,
                    showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条`
                  }}
                  rowSelection={{ type: 'radio', selectedRowKeys: selectTran ? [selectTran.ID] : [], onChange: this.onTransChange }}
                  onRow={(record) => ({ onClick: () => this.setState({ selectTran: record })})}
                  onRow={(record) => ({ onClick: () => {
                    if (this.forbid) {
                      this.forbid = false
                      return
                    }
                    this.onTransSelect(record)
                  }})}
                  onChange={this.changeTable}
                />
              </div>
              {/* <div className={'script-table' + (this.state.transTotal <= 10 ? ' no-footer' : '')}>
              {selectTran ? <div className="script-table">
                <div className="app-action">
                  <Button className="mk-green" onClick={() => this.setState({ scriptVisible: 'plus' })}>添加脚本</Button>
                  <Search placeholder="综合搜索" onSearch={value => this.scriptSearch(value)} enterButton />
                  <Button className="mk-green" onClick={() => this.setState({ scriptVisible: true })}>添加脚本</Button>
                  <Button className="mk-danger" onClick={this.deleteScripts} style={{marginLeft: '15px'}}>删除</Button>
                  <Search placeholder="综合搜索" defaultValue={this.state.scriptSearchKey} onSearch={value => this.scriptSearch(value)} enterButton />
                </div>
                <Table
                  rowKey="ID"
                  columns={transcolumns}
                  dataSource={translist}
                  columns={scriptcolumns}
                  dataSource={scriptlist}
                  pagination={{
                    current: this.state.transIndex,
                    current: this.state.scriptIndex,
                    pageSize: 10,
                    total: this.state.transTotal || 0
                    total: this.state.scriptTotal || 0,
                    showTotal: (total, range) => `${range[0]}-${range[1]} 共 ${total} 条`
                  }}
                  rowSelection={{ type: 'radio', selectedRowKeys: selectTran ? [selectTran.ID] : [], onChange: this.onTransChange }}
                  onRow={(record) => ({ onClick: () => this.setState({ selectTran: record })})}
                  onChange={this.changeTable}
                  rowSelection={{ type: 'checkbox', selectedRowKeys: selectScriptKeys, onChange: this.onScriptChange }}
                  onRow={(record) => ({ onClick: () => this.onScriptSelect(record)})}
                  onChange={this.changeScriptTable}
                />
              </div> */}
              </div> : null}
            </div>
            <div className="right-view">
              {selectApp ? <div className="app-title">{selectApp.remark}</div> : null}
@@ -813,8 +1047,8 @@
            title={'编辑应用'}
            width={'600px'}
            maskClosable={false}
            visible={!!visible}
            onCancel={() => this.setState({visible: false})}
            visible={visible !== false}
            onCancel={() => this.setState({visible: false, confirmloading: false})}
            confirmLoading={this.state.confirmloading}
            onOk={this.submitCard}
            cancelText="取消"
@@ -827,22 +1061,36 @@
            title={transVisible === 'plus' ? '添加传输号' : '编辑传输号'}
            width={'600px'}
            maskClosable={false}
            visible={!!transVisible}
            onCancel={() => this.setState({transVisible: false})}
            visible={transVisible !== false}
            onCancel={() => this.setState({transVisible: false, confirmloading: false})}
            confirmLoading={this.state.confirmloading}
            onOk={this.submitTrans}
            cancelText="取消"
            okText="确定"
            destroyOnClose
          >
            <TransForm type={transVisible} card={transVisible === 'edit' ? selectTran : ''} wrappedComponentRef={(inst) => this.transRef = inst} inputSubmit={this.submitTrans} />
            <TransForm type={transVisible} card={transVisible === 'edit' ? this.state.editTran : ''} wrappedComponentRef={(inst) => this.transRef = inst} inputSubmit={this.submitTrans} />
          </Modal>
          <Modal
            title={'添加脚本'}
            width={750}
            maskClosable={false}
            visible={scriptVisible}
            onCancel={() => this.setState({scriptVisible: false, confirmloading: false})}
            confirmLoading={this.state.confirmloading}
            onOk={this.submitScript}
            cancelText="取消"
            okText="确定"
            destroyOnClose
          >
            <ScriptForm applist={applist} wrappedComponentRef={(inst) => this.scriptRef = inst} inputSubmit={this.submitScript} />
          </Modal>
          <Modal
            title={'编辑子应用'}
            width={'850px'}
            maskClosable={false}
            visible={!!subVisible}
            onCancel={() => this.setState({subVisible: false})}
            visible={subVisible !== false}
            onCancel={() => this.setState({subVisible: false, confirmloading: false})}
            confirmLoading={this.state.confirmloading}
            onOk={this.submitSubCard}
            cancelText="取消"
src/views/appmanage/index.scss
@@ -3,6 +3,21 @@
  min-height: 100vh;
  padding: 70px 30px;
  .loading-mask {
    position: fixed;
    top: 0px;
    bottom: 0px;
    left: 0px;
    right: 0px;
    z-index: 2;
    background: rgba(255, 255, 255, 0.35);
    .ant-spin {
      position: absolute;
      left: 50%;
      top: 50%;
    }
  }
  .mob-header-container {
    padding-right: 0px;
    z-index: 10;
@@ -17,8 +32,8 @@
      flex: 1;
      width: 60%;
      padding-right: 5px;
      .trans-table {
        margin-top: 30px;
      .trans-table, .script-table {
        margin-top: 40px;
        .ant-input-search {
          width: 250px;
          float: right;
src/views/appmanage/scriptform/index.jsx
New file
@@ -0,0 +1,241 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Form, Row, Col, Input, Select, notification } from 'antd'
import moment from 'moment'
import Api from '@/api'
import Utils from '@/utils/utils.js'
import './index.scss'
const { TextArea } = Input
class ScriptForm extends Component {
  static propTpyes = {
    applist: PropTypes.array,
    inputSubmit: PropTypes.func
  }
  state = {
    type: 'subapp',
    sublist: [],
    views: [],
    appId: '',
    subAppId: ''
  }
  viewList = {}
  UNSAFE_componentWillMount() {
    const { applist } = this.props
    let sublist = []
    let appId = ''
    let subAppId = ''
    if (applist[0]) {
      sublist = applist[0].sublist || []
      appId = applist[0].ID + ',' + applist[0].kei_no
      if (sublist[0]) {
        subAppId = sublist[0].ID + ',' + sublist[0].lang + ',' + sublist[0].typename
      }
    }
    this.viewList = {}
    this.setState({sublist, appId, subAppId})
  }
  /**
   * @description 获取表单值
   */
  handleConfirm = () => {
    return new Promise(resolve => {
      this.props.form.validateFieldsAndScroll((err, values) => {
        if (!err) {
          resolve(values)
        }
      })
    })
  }
  changeType = (val) => {
    this.setState({type: val}, () => {
      this.getViews()
    })
  }
  changeApp = (val) => {
    const { applist } = this.props
    let app = applist.filter(item => `${item.ID},${item.kei_no}` === val)[0]
    let appId = ''
    let subAppId = ''
    let sublist = []
    if (app) {
      sublist = app.sublist || []
      appId = app.ID + ',' + app.kei_no
      if (sublist[0]) {
        subAppId = sublist[0].ID + ',' + sublist[0].lang + ',' + sublist[0].typename
      }
    }
    this.setState({sublist, subAppId, appId}, () => {
      this.getViews()
    })
    this.props.form.setFieldsValue({subAppId})
  }
  changeSubApp = (val) => {
    this.setState({subAppId: val}, () => {
      this.getViews()
    })
  }
  getViews = () => {
    const { type, appId, subAppId } = this.state
    if (type !== 'view' || !appId || !subAppId) return
    this.setState({views: []})
    this.props.form.setFieldsValue({viewId: ''})
    let kei = appId.split(',')[1]
    let m = subAppId.split(',')
    let _param = {
      func: 's_get_app_menus',
      TypeCharOne: kei,
      typename: m[2],
      lang: m[1],
      LText: `select '${window.GLOB.appkey}'`,
      timestamp: moment().format('YYYY-MM-DD HH:mm:ss')
    }
    _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp)
    Api.getCloudConfig(_param).then(res => {
      if (!res.status) {
        notification.warning({
          top: 92,
          message: res.message,
          duration: 5
        })
        return
      }
      this.setState({views: res.menus})
    })
  }
  /**
   * @description 回车提交
   */
  handleSubmit = (e) => {
    e.preventDefault()
    this.props.inputSubmit()
  }
  render() {
    const { applist } = this.props
    const { getFieldDecorator } = this.props.form
    const { sublist, appId, subAppId, type, views } = this.state
    const formItemLayout = {
      labelCol: {
        xs: { span: 24 },
        sm: { span: 8 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    }
    return (
      <Form {...formItemLayout} className="app-script-form">
        <Row gutter={24}>
          <Col span={12}>
            <Form.Item label="类型">
              {getFieldDecorator('VType', {
                initialValue: 'subapp',
                rules: [{
                  required: true,
                  message: '请选择类型!'
                }]
              })(
                <Select onChange={this.changeType}>
                  <Select.Option value="subapp">子应用</Select.Option>
                  <Select.Option value="view">页面</Select.Option>
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label="应用">
              {getFieldDecorator('appId', {
                initialValue: appId,
                rules: [{
                  required: true,
                  message: '请选择应用!'
                }]
              })(
                <Select onChange={this.changeApp}>
                  {applist.map(item => {
                    return <Select.Option key={item.ID} value={item.ID + ',' + item.kei_no}>{item.remark}</Select.Option>
                  })}
                </Select>
              )}
            </Form.Item>
          </Col>
          <Col span={12}>
            <Form.Item label="子应用">
              {getFieldDecorator('subAppId', {
                initialValue: subAppId,
                rules: [{
                  required: true,
                  message: '请选择子应用!'
                }]
              })(
                <Select onChange={this.changeSubApp}>
                  {sublist.map(item => {
                    return <Select.Option key={item.ID} value={item.ID + ',' + item.lang + ',' + item.typename}>{`${item.typename}(${item.lang !== 'zh-CN' ? '英文' : '中文'})${item.title || ''}`}</Select.Option>
                  })}
                </Select>
              )}
            </Form.Item>
          </Col>
          {type === 'view' ? <Col span={12}>
            <Form.Item label="页面">
              {getFieldDecorator('viewId', {
                initialValue: '',
                rules: [{
                  required: true,
                  message: '请选择页面!'
                }]
              })(
                <Select>
                  {views.map(item => {
                    return <Select.Option key={item.MenuID} value={item.MenuID}>{item.MenuName}</Select.Option>
                  })}
                </Select>
              )}
            </Form.Item>
          </Col> : null}
          <Col span={24} className="remark">
            <Form.Item label="说明">
              {getFieldDecorator('Remark', {
                initialValue: '',
                rules: [{
                  max: 512,
                  message: '说明不可超过512个字符!'
                }]
              })(<TextArea autoSize={{ minRows: 2, maxRows: 6 }} />)}
            </Form.Item>
          </Col>
        </Row>
      </Form>
    )
  }
}
export default Form.create()(ScriptForm)
src/views/appmanage/scriptform/index.scss
New file
@@ -0,0 +1,12 @@
.app-script-form {
  padding: 0px 24px 20px;
  .remark {
    .ant-form-item-label {
      width: 16%;
    }
    .ant-form-item-control-wrapper {
      width: 84%;
    }
  }
}
src/views/design/header/index.jsx
@@ -2,7 +2,7 @@
import { withRouter } from 'react-router-dom'
import {connect} from 'react-redux'
import { is, fromJS } from 'immutable'
import { Dropdown, Menu, Icon, Modal, Form, notification, Switch, Button } from 'antd'
import { Dropdown, Menu, Icon, Modal, notification, Switch, Button } from 'antd'
import asyncComponent from '@/utils/asyncComponent'
import {
@@ -327,6 +327,9 @@
        {!editLevel && options.sysType === 'local' && window.GLOB.systemType !== 'production' && this.props.memberLevel >= 20 ?
          <span onClick={() => {window.open('#/appmanage')}} className="mobile" type="edit"> 应用管理 <Icon type="arrow-right" /></span> : null
        }
        {!editLevel && options.sysType === 'local' && this.props.memberLevel >= 20 ?
          <span onClick={() => {window.open('#/interface')}} className="interface" type="edit"> 接口调试 <Icon type="arrow-right" /></span> : null
        }
        {/* window.btoa(window.encodeURIComponent(JSON.stringify({ MenuType: 'home', MenuId: 'home_page_id', MenuName: '首页' }))) */}
        {!editLevel && window.GLOB.systemType !== 'production' && this.props.memberLevel >= 20 ?
          <span className="home-edit" onClick={() => {window.open('#/menudesign/JTdCJTIyTWVudVR5cGUlMjIlM0ElMjJob21lJTIyJTJDJTIyTWVudUlkJTIyJTNBJTIyaG9tZV9wYWdlX2lkJTIyJTJDJTIyTWVudU5hbWUlMjIlM0ElMjIlRTklQTYlOTYlRTklQTElQjUlMjIlN0Q=')}}>
@@ -379,4 +382,4 @@
  }
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Form.create()(Header)))
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header))
src/views/design/header/index.scss
@@ -130,6 +130,13 @@
    color: #1890ff;
    cursor: pointer;
  }
  .interface {
    position: absolute;
    top: 170px;
    right: 50px;
    color: #1890ff;
    cursor: pointer;
  }
  .home-edit {
    position: absolute;
    top: 100px;
src/views/interface/api/index.js
New file
@@ -0,0 +1,175 @@
import axios from 'axios'
import md5 from 'md5'
import jsSHA from 'jssha'
import { notification } from 'antd'
window.GLOB.WebSql = null
if (window.openDatabase) {
  let service = window.GLOB.service ? '-' + window.GLOB.service.replace('/', '') : ''
  try {
    window.GLOB.WebSql = openDatabase(`mkdb${service}`, '1', 'mk-pc-database', 50 * 1024 * 1024)
    window.GLOB.WebSql.transaction(tx => {
      tx.executeSql('CREATE TABLE IF NOT EXISTS INTERFACES (uuid varchar(50), createDate varchar(50), method varchar(50), interface text, params text, headers text, active varchar(50), raw text, formData text, CDefine1 varchar(50), CDefine2 varchar(50), CDefine3 varchar(50), CDefine4 varchar(50), CDefine5 text)', [], () => {
      }, () => {
        // eslint-disable-next-line
        throw 'CREATE TABLE ERROR'
      })
    })
  } catch (e) {
    console.warn('WebSql 初始化失败!')
    window.GLOB.WebSql = null
  }
}
axios.defaults.crossDomain = true
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
axios.defaults.withCredentials = false
axios.interceptors.request.use((config) => {
  return config
}, (error) => {
  return Promise.reject(error)
})
axios.interceptors.response.use((response) => {
  return Promise.resolve(response)
}, (error) => {
  return Promise.reject(error)
})
class Api {
  /**
   * @description 登录系统, 获取用户信息
   */
  dologon (url, method, header, n) {
    let config = {
      url,
      method
    }
    if (header) {
      config.headers = header
    }
    if (n) {
      let _param = JSON.parse(n)
      let param = {}
      Object.keys(_param).forEach(key => {
        param[key.toLowerCase()] = _param[key]
      })
      if (param.type && param.username && param.password && param.timestamp) {
        if (param.type === 'S') {
          let shaObj = new jsSHA('SHA-1', 'TEXT')
          shaObj.update(param.password)
          param.password = shaObj.getHash('HEX').toUpperCase()
          param.password = md5(param.username + param.password + param.timestamp)
        } else if (/^mk_/ig.test(param.type)) {
          let shaObj = new jsSHA('SHA-1', 'TEXT')
          shaObj.update(param.password)
          param.password = shaObj.getHash('HEX').toUpperCase()
          param.password = md5(param.privatekey + param.username + param.password + param.timestamp)
          delete param.privatekey
        }
      }
      config.data = JSON.stringify(param)
    }
    return axios(config)
  }
  /**
   * @description 通用请求
   */
  normalRequest (url, method, header, n) {
    let config = {
      url,
      method
    }
    if (header) {
      config.headers = header
    }
    if (n) {
      config.data = n
    }
    return axios(config)
  }
  writeInWebSql (data) {
    if (!window.GLOB.WebSql) {
      notification.warning({ top: 92, message: 'WebSql开启失败!', duration: 5 })
      return
    }
    return new Promise((resolve, reject) => {
      window.GLOB.WebSql.transaction(tx => {
        tx.executeSql(`INSERT INTO INTERFACES (uuid, createDate, method, interface, params, headers, active, raw, formData) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, data, (tx, results) => {
          resolve(results)
        }, () => {
          resolve()
        })
      })
    })
  }
  getInterfaces () {
    if (!window.GLOB.WebSql) {
      notification.warning({ top: 92, message: 'WebSql开启失败!', duration: 5 })
      return
    }
    return new Promise((resolve, reject) => {
      window.GLOB.WebSql.transaction(tx => {
        tx.executeSql(`SELECT * FROM INTERFACES`, [], (tx, results) => {
          // let paramItem = results.rows[0]
          resolve(results)
        }, () => {
          window.GLOB.WebSql = null
          reject()
        })
      })
    })
  }
  delInterface (uuid) {
    if (!window.GLOB.WebSql) {
      notification.warning({ top: 92, message: 'WebSql开启失败!', duration: 5 })
      return
    }
    return new Promise((resolve, reject) => {
      window.GLOB.WebSql.transaction(tx => {
        tx.executeSql(`DELETE FROM INTERFACES where uuid = '${uuid}'`, [], (tx, results) => {
          resolve(results)
        }, () => {
          resolve()
        })
      })
    })
  }
  /**
   * @description 清空接口调用记录
   */
  clearInterfaces () {
    if (!window.GLOB.WebSql) {
      notification.warning({ top: 92, message: 'WebSql开启失败!', duration: 5 })
      return
    }
    return new Promise((resolve, reject) => {
      window.GLOB.WebSql.transaction(tx => {
        tx.executeSql(`DELETE FROM INTERFACES`, [], (tx, results) => {
          resolve(results)
        }, () => {
          resolve()
        })
      })
    })
  }
}
export default new Api()
src/views/interface/header/index.jsx
New file
@@ -0,0 +1,45 @@
import React, {Component} from 'react'
import { withRouter } from 'react-router-dom'
import Utils from '@/utils/utils.js'
import options from '@/store/options.js'
import avatar from '@/assets/img/avatar.jpg'
import MainLogo from '@/assets/img/main-logo.png'
import './index.scss'
class Header extends Component {
  state = {
    userName: sessionStorage.getItem('CloudUserName'),
    avatar: Utils.getrealurl(sessionStorage.getItem('CloudAvatar')),
  }
  UNSAFE_componentWillMount() {
    if (options.sysType !== 'local' || !sessionStorage.getItem('LoginUID')) {
      sessionStorage.clear()
      this.props.history.replace('/login')
    }
  }
  close = () => {
    window.close()
  }
  render () {
    return (
      <header className="interface-header-container">
        <div className="header-logo"><img src={MainLogo} alt=""/></div>
        <div className="title">接口调试</div>
        <div className="header-setting">
          <span className="close" onClick={this.close}>关闭</span>
          <img src={this.state.avatar || avatar} alt=""/>
          <span>
            <span className="username">{this.state.userName}</span>
          </span>
        </div>
      </header>
    )
  }
}
export default withRouter(Header)
src/views/interface/header/index.scss
New file
@@ -0,0 +1,63 @@
.interface-header-container {
  position: fixed;
  z-index: 20;
  left: 0;
  top: 0;
  font-weight: bold!important;
  width: 100%;
  height: 48px;
  background: #000;
  .header-logo {
    float: left;
    width: 180px;
    line-height: 48px;
    text-align: center;
    padding-left: 5px;
    box-sizing: border-box;
    opacity: 1;
    img {
      max-width: 100%;
      max-height: 40px;
    }
  }
  .title {
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    color: #ffffff;
    font-size: 18px;
  }
  .header-setting {
    float: right;
    line-height: 48px;
    margin-right: 10px;
    .close {
      margin-right: 20px;
      cursor: pointer;
      padding: 10px;
    }
    img {
      width: 29px;
      height: 29px;
      border-radius: 30px;
      margin-right: 7px;
    }
    span {
      color: #ffffff;
      font-size: 0.95rem;
      .username {
        display: inline-block;
        height: 30px;
        max-width: 95px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }
  }
}
src/views/interface/history/index.jsx
New file
@@ -0,0 +1,246 @@
import React, {Component} from 'react'
import { fromJS } from 'immutable'
import { Input, Modal, Empty, Icon } from 'antd'
import moment from 'moment'
import Api from '@/views/interface/api'
import Utils from '@/utils/utils.js'
import MKEmitter from '@/utils/events.js'
import './index.scss'
const { Search } = Input
const { confirm } = Modal
class History extends Component {
  state = {
    list: [],
    historys: [],
    searchKey: ''
  }
  componentDidMount() {
    MKEmitter.addListener('insertInterface', this.insertInterface)
    setTimeout(() => {
      Api.getInterfaces().then(res => {
        if (!res || !res.rows) return
        let rows = [...res.rows]
        rows.sort((a,b) => {
          return a.createDate < b.createDate ? 1 : -1
        })
        let list = []
        let item = null
        rows.forEach(m => {
          let date = m.createDate.substring(0, 10)
          if (m.params) {
            try {
              m.params = JSON.parse(m.params)
            } catch {
              m.params = []
            }
          } else {
            m.params = []
          }
          if (m.headers) {
            try {
              m.headers = JSON.parse(m.headers)
            } catch {
              m.headers = []
            }
          } else {
            m.headers = []
          }
          if (m.formData) {
            try {
              m.formData = JSON.parse(m.formData)
            } catch {
              m.formData = []
            }
          } else {
            m.formData = []
          }
          if (item && item.date !== date) {
            list.push(item)
            item = null
          }
          if (!item) {
            item = {date, sublist: []}
            item.sublist.push(m)
          } else if (item && item.date === date) {
            item.sublist.push(m)
          }
        })
        if (item) {
          list.push(item)
        }
        this.setState({list, historys: fromJS(list).toJS()})
      })
    }, 200)
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('insertInterface', this.insertInterface)
  }
  clear = () => {
    const _this = this
    confirm({
      content: 'Are you sure you want to clear all your history requests?',
      onOk() {
        Api.clearInterfaces().then(res => {
          if (res && res.rows.length === 0) {
            _this.setState({list: [], historys: []})
            Modal.success({
              title: '清除成功。'
            })
          } else {
            Modal.error({
              title: '清除失败!请刷新重试。'
            })
          }
        })
      },
      onCancel() {}
    })
  }
  delete = (m) => {
    const { searchKey } = this.state
    Api.delInterface(m.uuid).then(res => {
      if (res) {
        let list = this.state.list.filter(item => {
          item.sublist = item.sublist.filter(cell => cell.uuid !== m.uuid)
          return item.sublist.length > 0
        })
        let historys = fromJS(list).toJS()
        if (searchKey) {
          historys = historys.filter(item => {
            item.sublist = item.sublist.filter(cell => cell.interface.indexOf(searchKey) > -1)
            return item.sublist.length > 0
          })
        }
        this.setState({list, historys})
      } else {
        Modal.error({
          title: '删除失败!请刷新重试。'
        })
      }
    })
  }
  insertInterface = (item) => {
    item.uuid = Utils.getuuid()
    item.createDate = moment().format('YYYY-MM-DD HH:mm:ss')
    Api.writeInWebSql([item.uuid, item.createDate, item.method, item.interface, JSON.stringify(item.params), JSON.stringify(item.headers), item.active, item.raw, JSON.stringify(item.formData)]).then(res => {
      if (res) {
        let list = fromJS(this.state.list).toJS()
        if (list[0]) {
          if (list[0].date === item.createDate.substring(0, 10)) {
            list[0].sublist.unshift(item)
          } else {
            list.unshift({
              date: item.createDate.substring(0, 10),
              sublist: [item]
            })
          }
        } else {
          list.push({
            date: item.createDate.substring(0, 10),
            sublist: [item]
          })
        }
        let historys = fromJS(list).toJS()
        if (this.state.searchKey) {
          historys = historys.filter(item => {
            item.sublist = item.sublist.filter(cell => cell.interface.indexOf(this.state.searchKey) > -1)
            return item.sublist.length > 0
          })
        }
        this.setState({ list, historys })
      } else {
        Modal.error({
          title: '添加失败!请刷新重试。'
        })
      }
    })
  }
  use = (m) => {
    MKEmitter.emit('useInterface', fromJS(m).toJS())
  }
  changeSearch = (value) => {
    const { list } = this.state
    let historys = fromJS(list).toJS()
    if (value) {
      historys = historys.filter(item => {
        item.sublist = item.sublist.filter(cell => cell.interface.indexOf(value) > -1)
        return item.sublist.length > 0
      })
    }
    this.setState({searchKey: value, historys})
  }
  render () {
    const { historys } = this.state
    return (
      <aside className="interface-side-menu">
        <Search placeholder="Filter" onSearch={value => this.changeSearch(value)}/>
        <div className="title">
          History
          <span onClick={this.clear}>Clear all</span>
        </div>
        <div className="list-view">
          {historys.length === 0 ?
            <Empty /> :
            historys.map((item, index) => (
              <div className="list-line" key={index}>
                <div className="line-title">{item.date}</div>
                {item.sublist.map(m => (
                  <div className="line-item" key={m.uuid}>
                    <div className="method">POST</div>
                    <div className="inter">{m.interface}</div>
                    <div className="action">
                      <Icon type="delete" onClick={() => this.delete(m)} />
                      <Icon type="right" onClick={() => this.use(m)} />
                    </div>
                  </div>
                ))}
              </div>
            ))
          }
        </div>
      </aside>
    )
  }
}
export default History
src/views/interface/history/index.scss
New file
@@ -0,0 +1,113 @@
.interface-side-menu {
  display: inline-block;
  width: 300px;
  border-right: 2px solid #e8e8e8;
  height: 100%;
  .ant-input-search {
    margin: 10px 20px;
    width: calc(100% - 40px);
    .ant-input {
      border-radius: 20px;
    }
  }
  .title {
    padding-left: 20px;
    font-size: 16px;
    border-bottom: 1px solid #e8e8e8;
    padding-bottom: 3px;
    span {
      font-size: 14px;
      float: right;
      color: #fa541c;
      margin-right: 10px;
      cursor: pointer;
    }
  }
  .list-view {
    .ant-empty {
      margin-top: 50px;
    }
    .list-line {
      border-bottom: 1px solid #e8e8e8;
      .line-title {
        padding: 5px 15px;
        color: rgba(0, 0, 0, 0.85);
      }
      .line-item {
        position: relative;
        display: flex;
        padding-left: 25px;
        margin-bottom: 8px;
        transition: all 0.3s;
        .method {
          width: 42px;
          color: #faad14;
          font-size: 12px;
          font-weight: 600;
          line-height: 40px;
        }
        .inter {
          width: 225px;
          height: 42px;
          word-break: break-all;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-box-orient: vertical;
          -webkit-line-clamp: 2;
          overflow: hidden;
        }
        .action {
          width: 70px;
          position: absolute;
          line-height: 40px;
          right: 0px;
          height: 42px;
          background: #ffffff;
          padding-left: 15px;
          opacity: 0;
          transition: all 0.3s;
          i {
            cursor: pointer;
          }
          i:first-child {
            margin-right: 12px;
          }
          .anticon-delete {
            color: #fa541c;
          }
          .anticon-right {
            color: #1890ff;
          }
        }
      }
      .line-item:hover {
        background: #e9e9e9;
        .action {
          opacity: 1;
          background: #e9e9e9;
        }
      }
    }
  }
  .list-view {
    height: calc(100vh - 128px);
    overflow-x: hidden;
    overflow-y: auto;
  }
  .list-view::-webkit-scrollbar {
    width: 5px;
  }
  .list-view::-webkit-scrollbar-thumb {
    border-radius: 5px;
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.09);
    background: rgba(0, 0, 0, 0.09);
  }
  .list-view::-webkit-scrollbar-track {
    box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
    border-radius: 3px;
    border: 1px solid rgba(0, 0, 0, 0.07);
    background: rgba(0, 0, 0, 0);
  }
}
src/views/interface/index.jsx
New file
@@ -0,0 +1,36 @@
import React, {Component} from 'react'
import { ConfigProvider } from 'antd'
import enUS from 'antd/es/locale/en_US'
import zhCN from 'antd/es/locale/zh_CN'
// import Api from '@/views/interface/api'
// import asyncComponent from '@/utils/asyncComponent'
// import options from '@/store/options.js'
import Header from './header'
import History from './history'
import WorkSpace from './workspace'
import './index.scss'
const _locale = sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS
class Interface extends Component {
  componentDidMount() {
    //
  }
  render () {
    return (
      <div className="interface-view">
        <ConfigProvider locale={_locale}>
          <Header key="header"/>
          <History key="history"/>
          <WorkSpace key="workspace"/>
        </ConfigProvider>
      </div>
    )
  }
}
export default Interface
src/views/interface/index.scss
New file
@@ -0,0 +1,6 @@
.interface-view {
  padding-top: 48px;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
src/views/interface/workspace/editTable/index.jsx
New file
@@ -0,0 +1,255 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import { Table, Input, Form, Icon } from 'antd'
import Utils from '@/utils/utils.js'
import './index.scss'
const EditableContext = React.createContext()
const EditableRow = ({ form, index, ...props }) => (
  <EditableContext.Provider value={form}>
    <tr {...props} />
  </EditableContext.Provider>
)
const EditableFormRow = Form.create()(EditableRow)
class EditableCell extends Component {
  state = {
    editing: false
  }
  toggleEdit = () => {
    const { dataIndex } = this.props
    if (!dataIndex) return
    const editing = !this.state.editing
    this.setState({ editing }, () => {
      if (editing && this.input && this.input.select) {
        this.input.select()
      } else if (editing && this.input && this.input.focus) {
        this.input.focus()
      }
    })
  }
  save = e => {
    const { record, handleSave } = this.props
    this.form.validateFields((error, values) => {
      handleSave({ ...record, ...values })
      if (error && error[e.currentTarget.id]) {
        return
      }
      this.toggleEdit()
    })
  }
  renderCell = form => {
    this.form = form
    const { children, dataIndex, record } = this.props
    const { editing } = this.state
    return editing ? (
      <Form.Item style={{ margin: 0 }}>
        {form.getFieldDecorator(dataIndex, {
          initialValue: record[dataIndex]
        })(<Input ref={node => (this.input = node)} autoComplete="off" onPressEnter={this.save} onBlur={this.save} />)}
      </Form.Item>
    ) : (
      <div
        className="editable-cell-value-wrap"
        onClick={this.toggleEdit}
      >
        {children}
      </div>
    )
  }
  render() {
    const { dataIndex, title, record, index, handleSave, children, ...restProps } = this.props
    return (
      <td {...restProps}>
        <EditableContext.Consumer style={{padding: 0}}>{this.renderCell}</EditableContext.Consumer>
      </td>
    )
  }
}
class EditTable extends Component {
  static propTpyes = {
    data: PropTypes.array,
    onChange: PropTypes.func
  }
  state = {
    dataSource: [],
    selectedRowKeys: [],
    count: 0,
    columns: [{
      dataIndex: 'key',
      title: 'KEY',
      width: '33%'
    }, {
      dataIndex: 'value',
      title: 'VALUE',
      width: '33%'
    }, {
      dataIndex: 'description',
      title: 'DESCRIPTION',
      className: 'no-border',
      width: '33%'
    }, {
      dataIndex: '',
      title: '',
      width: '20px',
      render: (text, record) => {
        return (<Icon type="close" onClick={() => this.delete(record)}/>)
      }
    }]
  }
  UNSAFE_componentWillMount () {
    const { data } = this.props
    let _data = data || []
    let selectedRowKeys = []
    if (_data && _data.length > 0) {
      _data.forEach(item => {
        if (item.selected) {
          selectedRowKeys.push(item.uuid)
        }
      })
    } else {
      _data = [{
        uuid: Utils.getuuid(),
        key: '',
        value: '',
        description: '',
        selected: true
      }]
      selectedRowKeys.push(_data[0].uuid)
      this.props.onChange(_data)
    }
    this.setState({
      dataSource: _data,
      selectedRowKeys
    })
  }
  handleDelete = key => {
    const { dataSource } = this.state
    let _data = dataSource.filter(item => item.key !== key)
    this.setState({ dataSource: _data }, () => {
      this.props.onChange(_data)
    })
  }
  delete = (item) => {
    const { dataSource, selectedRowKeys } = this.state
    let _data = dataSource.filter(cell => cell.uuid !== item.uuid)
    let _keys = selectedRowKeys.filter(key => key !== item.uuid)
    if (_data.length === 0) {
      _data = [{
        uuid: Utils.getuuid(),
        key: '',
        value: '',
        description: '',
        selected: true
      }]
      _keys = [_data[0].uuid]
      this.props.onChange(_data)
    }
    this.setState({
      dataSource: _data,
      selectedRowKeys: _keys
    }, () => {
      this.props.onChange(_data)
    })
  }
  handleAdd = (e) => {
    e.stopPropagation()
    const { dataSource, selectedRowKeys } = this.state
    const item = {
      uuid: Utils.getuuid(),
      key: '',
      value: '',
      description: '',
      selected: true
    }
    let _data = [...dataSource, item]
    this.setState({
      dataSource: _data,
      selectedRowKeys: [...selectedRowKeys, item.uuid]
    }, () => {
      this.props.onChange(_data)
    })
  }
  handleSave = row => {
    const newData = this.state.dataSource.map(item => {
      if (row.uuid === item.uuid) return row
      return item
    })
    this.setState({ dataSource: newData }, () => {
      this.props.onChange(newData)
    })
  }
  onChange = selectedRowKeys => {
    const newData = this.state.dataSource.map(item => {
      item.selected = selectedRowKeys.includes(item.uuid)
      return item
    })
    this.setState({ dataSource: newData, selectedRowKeys }, () => {
      this.props.onChange(newData)
    })
  }
  render() {
    const { dataSource, selectedRowKeys } = this.state
    const components = {
      body: {
        row: EditableFormRow,
        cell: EditableCell
      }
    }
    const columns = this.state.columns.map(col => {
      return {
        ...col,
        onCell: record => ({
          record,
          dataIndex: col.dataIndex,
          title: col.title,
          handleSave: this.handleSave,
        })
      }
    })
    return (
      <div className="params-edit-table">
        <Icon className="add-row" type="plus" onClick={this.handleAdd} />
        <Table
          rowKey="uuid"
          components={components}
          bordered
          dataSource={dataSource}
          columns={columns}
          rowSelection={{ type: 'checkbox', selectedRowKeys, onChange: this.onChange }}
          pagination={false}
        />
      </div>
    )
  }
}
export default EditTable
src/views/interface/workspace/editTable/index.scss
New file
@@ -0,0 +1,85 @@
.params-edit-table {
  .add-row {
    position: absolute;
    z-index: 1;
    right: 10px;
    top: 0px;
    padding: 5px;
    font-size: 22px;
    color: #26C281;
  }
  .ant-table table {
    border-radius: 0;
  }
  .no-border {
    border-right: 0!important;
  }
  .ant-table-thead > tr {
    > th {
      padding: 10px 10px;
    }
    .ant-table-selection-column {
      .ant-table-header-column {
        display: none;
      }
    }
  }
  .ant-table-tbody {
    > tr {
      > td {
        background: #ffffff!important;
        padding: 2px 10px;
        .ant-input {
          height: 28px;
        }
        .ant-form-item {
          height: 30px;
          line-height: 30px;
          .ant-form-item-control {
            line-height: 30px;
          }
        }
        .anticon-close {
          opacity: 0;
        }
      }
    }
    > tr:hover {
      > td {
        background: #ffffff!important;
        .anticon-close {
          opacity: 1;
        }
      }
    }
  }
  .editable-cell-value-wrap {
    cursor: pointer;
    height: 36px;
    width: 300px;
    display: table-cell;
    vertical-align: middle;
    word-wrap: break-word;
    word-break: break-word;
    .ant-input {
      height: 30px;
      padding: 0 11px;
    }
  }
  .ant-form-item-control-wrapper {
    width: 100%;
  }
  .ant-table-placeholder {
    padding: 5px 16px;
    .ant-empty-normal {
      margin: 0;
    }
  }
  .operation-btn {
    margin-right: 10px;
    cursor: pointer;
  }
  .ant-form-explain {
    font-size: 12px;
  }
}
src/views/interface/workspace/index.jsx
New file
@@ -0,0 +1,115 @@
import React, {Component} from 'react'
import { Icon, Tabs } from 'antd'
import Utils from '@/utils/utils.js'
import MKEmitter from '@/utils/events.js'
import Request from './request'
import './index.scss'
import { fromJS } from 'immutable'
class WorkSpace extends Component {
  state = {
    tabviews: [{
      uuid: Utils.getuuid(),
      createDate: '',
      method: 'POST',
      interface: '',
      params: [],
      headers: [],
      active: 'raw',
      raw: '',
      formData: []
    }]
  }
  componentDidMount() {
    MKEmitter.addListener('useInterface', this.useInterface)
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
    MKEmitter.removeListener('useInterface', this.useInterface)
  }
  useInterface = (item) => {
    item.uuid = Utils.getuuid()
    this.setState({tabviews: [...this.state.tabviews, item]})
  }
  handleTabview = (view) => {
    let tabviews = fromJS(this.state.tabviews).toJS()
    tabviews = tabviews.filter(item => item.uuid !== view.uuid)
    this.setState({tabviews}, () => {
      if (tabviews.length > 0) return
      setTimeout(() => {
        this.setState({tabviews: [{
          uuid: Utils.getuuid(),
          createDate: '',
          method: 'POST',
          interface: '',
          params: [],
          headers: [],
          active: 'raw',
          raw: '',
          formData: []
        }]})
      }, 300)
    })
  }
  handleAdd = () => {
    this.setState({tabviews: [...this.state.tabviews, {
      uuid: Utils.getuuid(),
      createDate: '',
      method: 'POST',
      interface: '',
      params: [],
      headers: [],
      active: 'raw',
      raw: '',
      formData: []
    }]})
  }
  render () {
    const { tabviews } = this.state
    return (
      <div className="workspace-wrap">
        <Icon className="add-view" type="plus" onClick={this.handleAdd} />
        <Tabs type="card">
          {tabviews.map(view => {
            return (
              <Tabs.TabPane
                tab={
                  <span className="control">
                    <span className="method">
                      {view.method || 'POST'}
                    </span>
                    <span className="interface">
                      {view.interface || 'Untitled Request'}
                    </span>
                    <Icon type="close" onClick={() => this.handleTabview(view)}/>
                  </span>
                }
                key={view.uuid}
              >
                <Request config={view} />
              </Tabs.TabPane>
            )
          })}
        </Tabs>
      </div>
    )
  }
}
export default WorkSpace
src/views/interface/workspace/index.scss
New file
@@ -0,0 +1,52 @@
.workspace-wrap {
  display: inline-block;
  width: calc(100vw - 300px);
  height: 100%;
  vertical-align: top;
  padding-top: 10px;
  position: relative;
  .add-view {
    position: absolute;
    right: 25px;
    top: 12px;
    z-index: 2;
    padding: 5px;
    cursor: pointer;
    font-size: 22px;
    color: #26C281;
  }
  .ant-tabs.ant-tabs-card .ant-tabs-card-bar {
    >.ant-tabs-nav-container {
      margin-right: 100px;
    }
    padding-left: 5px;
    .ant-tabs-tab {
      margin-right: 7px;
      padding: 0 10px;
      .method {
        color: #faad14;
        font-size: 12px;
        font-weight: 600;
        margin-right: 5px;
        vertical-align: top;
      }
      .interface {
        display: inline-block;
        color: rgba(0, 0, 0, 0.65);
        width: 200px;
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
        vertical-align: top;
      }
      .anticon-close {
        color:rgba(0, 0, 0, 0.65);
        margin: 0;
      }
    }
    .ant-tabs-tab.ant-tabs-tab-active {
      border-top-color: #fa541c;
    }
  }
}
src/views/interface/workspace/request/index.jsx
New file
@@ -0,0 +1,300 @@
import React, {Component} from 'react'
import { fromJS } from 'immutable'
import md5 from 'md5'
import { Input, Select, Button, Tabs, Radio, Modal } from 'antd'
import Api from '@/views/interface/api'
import Utils from '@/utils/utils.js'
import EditTable from '../editTable'
import CodeMirror from '@/templates/zshare/codemirror'
import MKEmitter from '@/utils/events.js'
import './index.scss'
const InputGroup = Input.Group
const { Option } = Select
class Request extends Component {
  state = {
    active: 'raw',
    body: null,
    response: null,
    status: ''
  }
  UNSAFE_componentWillMount() {
    const { config } = this.props
    this.setState({active: config.active, config: fromJS(config).toJS()})
  }
  componentDidMount() {
  }
  /**
   * @description 组件销毁,清除state更新
   */
  componentWillUnmount () {
    this.setState = () => {
      return
    }
  }
  onChange = (e) => {
    const { config } = this.state
    this.setState({active: e.target.value, config: {...config, active: e.target.value}})
  }
  rawChange = (val) => {
    const { config } = this.state
    this.setState({config: {...config, raw: val}})
  }
  changeFormData = (vals) => {
    const { config } = this.state
    this.setState({config: {...config, formData: vals}})
  }
  changeHeader = (vals) => {
    const { config } = this.state
    this.setState({config: {...config, headers: vals}})
  }
  changeParams = (vals) => {
    const { config } = this.state
    this.setState({config: {...config, params: vals}})
  }
  changeMethod = (val) => {
    const { config } = this.state
    this.setState({config: {...config, method: val}})
  }
  changeInter = (e) => {
    const { config } = this.state
    this.setState({config: {...config, interface: e.target.value}})
  }
  send = () => {
    const { config } = this.state
    let raws = null
    if (!config.interface) {
      Modal.error({
        title: '请填入接口地址!'
      })
      return
    } else if (config.active === 'raw') {
      if (config.raw) {
        try {
          raws = JSON.parse(config.raw)
        } catch {
          Modal.error({
            title: '参数格式错误,必须为JSON格式!'
          })
          return
        }
      }
    }
    let url = config.interface
    let m = []
    config.params.forEach(item => {
      if (!item.selected || !item.key) return
      m.push(`${item.key}=${item.value}`)
    })
    m = m.join('&')
    if (m) {
      if (/\?/ig.test(url)) {
        url = url + '&' + m
      } else {
        url = url + '?' + m
      }
    }
    let header = null
    config.headers.forEach(item => {
      if (!item.selected || !item.key) return
      header = header || {}
      header[item.key] = item.value
    })
    let n = null
    if (config.active === 'raw' && raws) {
      n = JSON.stringify(raws)
    } else if (config.active === 'formData') {
      config.formData.forEach(item => {
        if (!item.selected || !item.key) return
        n = n || {}
        n[item.key] = item.value
      })
      n = JSON.stringify(n)
    }
    if (/logon/ig.test(url)) {
      Api.dologon(url, config.method, header, n).then(res => {
        this.handleResponse(res)
      }, (err) => {
        this.handleResponse(err)
      })
    } else if (/dostars/ig.test(url)) {
      if (n) {
        n = JSON.parse(n)
        n = this.encryptParam(n)
        n = JSON.stringify(n)
      }
      Api.normalRequest(url, config.method, header, n).then(res => {
        this.handleResponse(res)
      }, (err) => {
        this.handleResponse(err)
      })
    } else {
      Api.normalRequest(url, config.method, header, n).then(res => {
        this.handleResponse(res)
      }, (err) => {
        this.handleResponse(err)
      })
    }
  }
  handleResponse = (res) => {
    let body = null
    if (res && res.data) {
      try {
        body = JSON.stringify(res.data, null, 2)
        body = body.replace(/\n/ig, '<br/>')
        body = body.replace(/\s/ig, '&nbsp;')
      } catch {
        body = null
      }
    }
    let status = ''
    if (res.status) {
      status = res.status + res.statusText
    } else if (res.name === 'Error') {
      status = res.message
      body = `请求异常${status === 'Network Error' ? ',可能原因:1、网络异常;2、接口跨域。' : '!'}`
    }
    this.setState({response: res, body, status})
  }
  encryptParam (param) {
    param.nonc = Utils.getuuid()
    let keys = Object.keys(param).sort()
    let values = ''
    keys.forEach(key => {
      if (key.toLowerCase() === 'rduri' || key.toLowerCase() === 't') return
      if (param[key] === undefined) {
        delete param[key]
      } else if (typeof(param[key]) === 'object') {
        values += key + JSON.stringify(param[key])
      } else {
        values += key + param[key]
      }
    })
    param.sign = md5(values)
    param.t = new Date().getTime()
    return param
  }
  save = () => {
    const { config } = this.state
    if (!config.interface) {
      Modal.error({
        title: '请填入接口地址!'
      })
      return
    }
    MKEmitter.emit('insertInterface', config)
  }
  render () {
    const { active, config, response, body, status } = this.state
    let hasParam = config.params.filter(item => item.selected && item.key).length > 0
    let hasHeader = config.headers.filter(item => item.selected && item.key).length
    let hasBody = false
    if (active === 'raw' && config.raw) {
      hasBody = true
    } else if (active === 'formData' && config.formData.filter(item => item.selected && item.key).length > 0) {
      hasBody = true
    }
    return (
      <div className="request-wrap">
        <div className="request-interface">
          <InputGroup compact>
            <Select defaultValue={config.method} onChange={this.changeMethod}>
              <Option value="POST">POST</Option>
              <Option value="GET">GET</Option>
            </Select>
            <Input placeholder="Enter request URL" defaultValue={config.interface} onChange={this.changeInter}/>
          </InputGroup>
          <Button type="primary" onClick={this.send}>Send</Button>
          <Button onClick={this.save}>Save</Button>
        </div>
        <Tabs animated={false}>
          <Tabs.TabPane forceRender={true} tab={<span className={hasParam ? 'active' : ''}>Params</span>} key="Params">
            <EditTable data={config.params} onChange={this.changeParams}/>
          </Tabs.TabPane>
          <Tabs.TabPane forceRender={true} tab={<span>Headers{hasHeader ? <span className="number">{`(${hasHeader})`}</span> : ''}</span>} key="Headers">
            <EditTable data={config.headers} onChange={this.changeHeader}/>
          </Tabs.TabPane>
          <Tabs.TabPane forceRender={true} tab={<span className={hasBody ? 'active' : ''}>Body</span>} key="Body">
            <div className="body-class">
              <Radio.Group onChange={this.onChange} value={active}>
                <Radio value={'none'}>none</Radio>
                <Radio value={'formData'}>formData</Radio>
                <Radio value={'raw'}>raw</Radio>
              </Radio.Group>
            </div>
            <div className={'body-content ' + (active === 'none' ? 'show' : '')}>
              <div className="no-body">This request does not have a body</div>
            </div>
            <div className={'body-content ' + (active === 'formData' ? 'show' : '')}>
              <EditTable data={config.formData} onChange={this.changeFormData}/>
            </div>
            <div className={'body-content ' + (active === 'raw' ? 'show' : '')}>
              <CodeMirror value={config.raw} mode="text/javascript" onChange={this.rawChange} />
            </div>
          </Tabs.TabPane>
        </Tabs>
        <div className="response">
          {response ? <div className="header">
            {body ? 'Body' : 'Response'}
            {status ? <span className="status">Status: <span className={status === '200OK' ? 'green' : ''}>{status}</span></span> : null}
          </div> : <div className="header">
            Response
            <span className="empty">Hit the Send button to get a response.</span>
          </div>}
          <div style={{paddingLeft: '3px', paddingTop: '3px'}} dangerouslySetInnerHTML={{__html: body}}></div>
        </div>
      </div>
    )
  }
}
export default Request
src/views/interface/workspace/request/index.scss
New file
@@ -0,0 +1,141 @@
.request-wrap {
  position: relative;
  padding: 0px 10px;
  height: calc(100vh - 115px);
  overflow-x: hidden;
  overflow-y: auto;
  .request-interface {
    background: #ffffff;
    margin-bottom: 10px;
    .ant-select-selection {
      height: 40px;
      width: 90px;
    }
    .ant-select-selection__rendered {
      line-height: 40px;
    }
    .ant-input {
      height: 40px;
      width: calc(100% - 100px);
    }
    .ant-input-group {
      display: inline-block;
      width: calc(100% - 210px);
      vertical-align: top;
    }
    .ant-btn {
      height: 40px;
      width: 90px;
    }
    .ant-btn-primary {
      margin-right: 10px;
    }
  }
  .ant-tabs.ant-tabs-line {
    .ant-tabs-top-bar {
      margin-bottom: 0px;
      border: 0;
      .ant-tabs-nav {
        .ant-tabs-tab {
          color: rgba(0, 0, 0, 0.65);
          padding-bottom: 5px;
          span {
            position: relative;
          }
          .number {
            color: #26C281;
            margin-left: 3px;
            font-size: 13px;
          }
          span.active::after {
            content: ' ';
            display: block;
            position: absolute;
            right: -12px;
            top: 2px;
            width: 8px;
            height: 8px;
            border-radius: 8px;
            background-color: #26C281;
          }
        }
        .ant-tabs-tab.ant-tabs-tab-active {
          color: rgba(0, 0, 0, 0.85);
        }
      }
      .ant-tabs-ink-bar {
        background-color: #fa541c;
      }
    }
  }
  .body-class {
    height: 40px;
    line-height: 40px;
    padding-left: 15px;
    border-top: 1px solid #e8e8e8;
  }
  .body-content {
    display: none;
    .no-body {
      color: rgba(0, 0, 0, 0.45);
      border-top: 1px solid #e8e8e8;
      text-align: center;
      height: 40px;
      line-height: 40px;
    }
  }
  .body-content.show {
    display: block;
  }
  .anticon-fullscreen {
    display: none;
  }
  .code-mirror-wrap .code-mirror-area {
    border-radius: 0;
  }
  .response {
    min-height: 400px;
    border-left: 1px solid #e8e8e8;
    border-right: 1px solid #e8e8e8;
    .header {
      position: relative;
      height: 40px;
      background-color: #fafafa;
      line-height: 40px;
      padding-left: 10px;
      border-bottom: 1px solid #e8e8e8;
      .empty {
        position: absolute;
        top: 150px;
        left: 50%;
        transform: translateX(-50%);
        font-size: 20px;
        color:rgba(0, 0, 0, 0.45)
      }
      .status {
        font-size: 13px;
        float: right;
        margin-right: 40px;
        span.green {
          color: #26C281;
        }
      }
    }
  }
}
.request-wrap::-webkit-scrollbar {
  width: 5px;
}
.request-wrap::-webkit-scrollbar-thumb {
  border-radius: 5px;
  box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.09);
  background: rgba(0, 0, 0, 0.09);
}
.request-wrap::-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);
}