import React, { PureComponent as Component } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import _ from 'underscore' import constants from '../../../../constants/variable.js' import { handlePath, nameLengthLimit } from '../../../../common.js' import { changeEditStatus } from '../../../../reducer/modules/interface.js' import json5 from 'json5' import { message, Affix, Tabs, Modal } from 'antd' import EasyDragSort from '../../../../components/EasyDragSort/EasyDragSort.js' import mockEditor from 'client/components/AceEditor/mockEditor' import AceEditor from 'client/components/AceEditor/AceEditor' import axios from 'axios' import { MOCK_SOURCE } from '../../../../constants/variable.js' import Editor from 'common/tui-editor/dist/tui-editor-Editor-all.min.js' const jSchema = require('json-schema-editor-visual') const ResBodySchema = jSchema({ lang: 'zh_CN', mock: MOCK_SOURCE }) const ReqBodySchema = jSchema({ lang: 'zh_CN', mock: MOCK_SOURCE }) const TabPane = Tabs.TabPane require('common/tui-editor/dist/tui-editor.min.css') // editor ui require('common/tui-editor/dist/tui-editor-contents.min.css') // editor content require('./editor.css') function checkIsJsonSchema(json) { try { json = json5.parse(json) if (json.properties && typeof json.properties === 'object' && !json.type) { json.type = 'object' } if (json.items && typeof json.items === 'object' && !json.type) { json.type = 'array' } if (!json.type) { return false } json.type = json.type.toLowerCase() let types = ['object', 'string', 'number', 'array', 'boolean', 'integer'] if (types.indexOf(json.type) === -1) { return false } return JSON.stringify(json) } catch (e) { return false } } let EditFormContext const validJson = json => { try { json5.parse(json) return true } catch (e) { return false } } import { Form, Select, Input, Tooltip, Button, Row, Col, Radio, Icon, AutoComplete, Switch } from 'antd' const Json5Example = ` { /** * info */ "id": 1 //appId } ` const TextArea = Input.TextArea const FormItem = Form.Item const Option = Select.Option const InputGroup = Input.Group const RadioButton = Radio.Button const RadioGroup = Radio.Group const dataTpl = { req_query: { name: '', required: '1', desc: '', example: '', type: 'string' }, req_headers: { name: '', required: '1', desc: '', example: '' }, req_params: { name: '', desc: '', example: '', type: 'string' }, req_body_form: { name: '', type: 'text', required: '1', desc: '', example: '' } } const HTTP_METHOD = constants.HTTP_METHOD const HTTP_METHOD_KEYS = Object.keys(HTTP_METHOD) const HTTP_REQUEST_HEADER = constants.HTTP_REQUEST_HEADER @connect( state => { return { custom_field: state.group.field, projectMsg: state.project.currProject } }, { changeEditStatus }, ) class InterfaceEditForm extends Component { static propTypes = { custom_field: PropTypes.object, groupList: PropTypes.array, form: PropTypes.object, curdata: PropTypes.object, mockUrl: PropTypes.string, onSubmit: PropTypes.func, basepath: PropTypes.string, noticed: PropTypes.bool, cat: PropTypes.array, changeEditStatus: PropTypes.func, projectMsg: PropTypes.object, onTagClick: PropTypes.func } initState(curdata) { this.startTime = new Date().getTime() if (curdata.req_query && curdata.req_query.length === 0) { delete curdata.req_query } if (curdata.req_headers && curdata.req_headers.length === 0) { delete curdata.req_headers } if (curdata.req_body_form && curdata.req_body_form.length === 0) { delete curdata.req_body_form } if (curdata.req_params && curdata.req_params.length === 0) { delete curdata.req_params } if (curdata.req_body_form) { curdata.req_body_form = curdata.req_body_form.map(item => { item.type = item.type === 'text' ? 'text' : 'file' return item }) } // 设置标签的展开与折叠 curdata['hideTabs'] = { req: { body: 'hide', query: 'hide', headers: 'hide' } } curdata['hideTabs']['req'][HTTP_METHOD[curdata.method].default_tab] = '' return Object.assign( { submitStatus: false, title: '', path: '', status: 'undone', method: 'get', req_params: [], req_query: [ { name: '', desc: '', required: '1', type: 'string' } ], req_headers: [ { name: '', value: '', required: '1' } ], req_body_type: 'form', req_body_form: [ { name: '', type: 'text', required: '1' } ], req_body_other: '', res_body_type: 'json', res_body: '', desc: '', res_body_mock: '', jsonType: 'tpl', mockUrl: this.props.mockUrl, req_radio_type: 'req-query', custom_field_value: '', api_opened: false, visible: false, headerBulkAddVisible: false, headerBulkValue: '' }, curdata, ) } constructor(props) { super(props) const { curdata } = this.props // console.log('custom_field1', this.props.custom_field); this.state = this.initState(curdata) } handleSubmit = e => { e.preventDefault() this.setState({ submitStatus: true }) try { this.props.form.validateFields((err, values) => { setTimeout(() => { if (this._isMounted) { this.setState({ submitStatus: false }) } }, 3000) if (!err) { values.desc = this.editor.getHtml() values.markdown = this.editor.getMarkdown() if (values.res_body_type === 'json') { if ( this.state.res_body && validJson(this.state.res_body) === false ) { return message.error('返回body json格式有问题,请检查!') } try { values.res_body = JSON.stringify( JSON.parse(this.state.res_body), null, ' ', ) } catch (e) { values.res_body = this.state.res_body } } if (values.req_body_type === 'json') { if ( this.state.req_body_other && validJson(this.state.req_body_other) === false ) { return message.error('响应Body json格式有问题,请检查!') } try { values.req_body_other = JSON.stringify( JSON.parse(this.state.req_body_other), null, ' ', ) } catch (e) { values.req_body_other = this.state.req_body_other } } values.method = this.state.method values.req_params = values.req_params || [] values.req_headers = values.req_headers || [] values.req_body_form = values.req_body_form || [] let isfile = false, isHaveContentType = false if (values.req_body_type === 'form') { values.req_body_form.forEach(item => { if (item.type === 'file') { isfile = true } }) values.req_headers.map(item => { if (item.name === 'Content-Type') { item.value = isfile ? 'multipart/form-data' : 'application/x-www-form-urlencoded' isHaveContentType = true } }) if (isHaveContentType === false) { values.req_headers.unshift({ name: 'Content-Type', value: isfile ? 'multipart/form-data' : 'application/x-www-form-urlencoded' }) } } else if (values.req_body_type === 'json') { values.req_headers ? values.req_headers.map(item => { if (item.name === 'Content-Type') { item.value = 'application/json' isHaveContentType = true } }) : [] if (isHaveContentType === false) { values.req_headers = values.req_headers || [] values.req_headers.unshift({ name: 'Content-Type', value: 'application/json' }) } } values.req_headers = values.req_headers ? values.req_headers.filter(item => item.name !== '') : [] values.req_body_form = values.req_body_form ? values.req_body_form.filter(item => item.name !== '') : [] values.req_params = values.req_params ? values.req_params.filter(item => item.name !== '') : [] values.req_query = values.req_query ? values.req_query.filter(item => item.name !== '') : [] if (HTTP_METHOD[values.method].request_body !== true) { values.req_body_form = [] } if ( values.req_body_is_json_schema && values.req_body_other && values.req_body_type === 'json' ) { values.req_body_other = checkIsJsonSchema(values.req_body_other) if (!values.req_body_other) { return message.error('请求参数 json-schema 格式有误') } } if ( values.res_body_is_json_schema && values.res_body && values.res_body_type === 'json' ) { values.res_body = checkIsJsonSchema(values.res_body) if (!values.res_body) { return message.error('返回数据 json-schema 格式有误') } } this.props.onSubmit(values) EditFormContext.props.changeEditStatus(false) } }) } catch (e) { console.error(e.message) this.setState({ submitStatus: false }) } } onChangeMethod = val => { let radio = [] if (HTTP_METHOD[val].request_body) { radio = ['req', 'body'] } else { radio = ['req', 'query'] } this.setState({ req_radio_type: radio.join('-') }) this.setState({ method: val }, () => { this._changeRadioGroup(radio[0], radio[1]) }) } componentDidMount() { EditFormContext = this this._isMounted = true this.setState({ req_radio_type: HTTP_METHOD[this.state.method].request_body ? 'req-body' : 'req-query' }) this.mockPreview = mockEditor({ container: 'mock-preview', data: '', readOnly: true }) this.editor = new Editor({ el: document.querySelector('#desc'), initialEditType: 'wysiwyg', height: '500px', initialValue: this.state.markdown || this.state.desc }) } componentWillUnmount() { EditFormContext.props.changeEditStatus(false) EditFormContext = null this._isMounted = false } addParams = (name, data) => { let newValue = {} data = data || dataTpl[name] newValue[name] = [].concat(this.state[name], data) this.setState(newValue) } delParams = (key, name) => { let curValue = this.props.form.getFieldValue(name) let newValue = {} newValue[name] = curValue.filter((val, index) => { return index !== key }) this.props.form.setFieldsValue(newValue) this.setState(newValue) } handleMockPreview = async () => { let str = '' try { if (this.props.form.getFieldValue('res_body_is_json_schema')) { let schema = json5.parse(this.props.form.getFieldValue('res_body')) let result = await axios.post('/api/interface/schema2json', { schema: schema }) return this.mockPreview.setValue(JSON.stringify(result.data)) } if (this.resBodyEditor.editor.curData.format === true) { str = JSON.stringify( this.resBodyEditor.editor.curData.mockData(), null, ' ', ) } else { str = '解析出错: ' + this.resBodyEditor.editor.curData.format } } catch (err) { str = '解析出错: ' + err.message } this.mockPreview.setValue(str) } handleJsonType = key => { key = key || 'tpl' if (key === 'preview') { this.handleMockPreview() } this.setState({ jsonType: key }) } handlePath = e => { let val = e.target.value, queue = [] let insertParams = name => { let findExist = _.find(this.state.req_params, { name: name }) if (findExist) { queue.push(findExist) } else { queue.push({ name: name, desc: '', type: 'string' }) } } val = handlePath(val) this.props.form.setFieldsValue({ path: val }) if (val && val.indexOf(':') !== -1) { let paths = val.split('/'), name, i for (i = 1; i < paths.length; i++) { if (paths[i][0] === ':') { name = paths[i].substr(1) insertParams(name) } } } if (val && val.length > 3) { val.replace(/\{(.+?)\}/g, function (str, match) { insertParams(match) }) } this.setState({ req_params: queue }) } // 点击切换radio changeRadioGroup = e => { const res = e.target.value.split('-') if (res[0] === 'req') { this.setState({ req_radio_type: e.target.value }) } this._changeRadioGroup(res[0], res[1]) } _changeRadioGroup = (group, item) => { const obj = {} // 先全部隐藏 for (let key in this.state.hideTabs[group]) { obj[key] = 'hide' } // 再取消选中项目的隐藏 obj[item] = '' this.setState({ hideTabs: { ...this.state.hideTabs, [group]: obj } }) } handleDragMove = name => { return data => { let newValue = { [name]: data } this.props.form.setFieldsValue(newValue) this.setState(newValue) } } // 处理res_body Editor handleResBody = d => { const initResBody = this.state.res_body this.setState({ res_body: d.text }) EditFormContext.props.changeEditStatus(initResBody !== d.text) } // 处理 req_body_other Editor handleReqBody = d => { const initReqBody = this.state.req_body_other this.setState({ req_body_other: d.text }) EditFormContext.props.changeEditStatus(initReqBody !== d.text) } // 处理批量导入参数 handleBulkOk = () => { let curValue = this.props.form.getFieldValue(this.state.bulkName) || [] // { name: '', required: '1', desc: '', example: '' } let newValue = [] this.state.bulkValue.split('\n').forEach((item, index) => { let valueItem = Object.assign( {}, curValue[index] || dataTpl[this.state.bulkName], ) valueItem.name = item.split(':')[0] valueItem.type = item.split(':')[1] || '' valueItem.example = item.split(':')[2] || '' newValue.push(valueItem) }) this.props.form.setFieldsValue({[this.state.bulkName]: newValue}); this.setState({ visible: false, bulkValue: null, bulkName: null, [this.state.bulkName]: newValue }) } // 取消批量导入参数 handleBulkCancel = () => { this.setState({ visible: false, bulkValue: null, bulkName: null }) } showBulk = name => { let value = this.props.form.getFieldValue(name) let bulkValue = `` if (value) { value.forEach(item => { return (bulkValue += item.name ? `${item.name}:${item.type}:${item.example || ''}\n` : '') }) } this.setState({ visible: true, bulkValue, bulkName: name }) } handleBulkValueInput = e => { this.setState({ bulkValue: e.target.value }) } handleHeaderBulkAddClick = () => { const headerBulkValue = (this.state.req_headers || []) .map(item => `${item.name}: ${item.value}`) .join('\n') this.setState({ headerBulkAddVisible: true, headerBulkValue: headerBulkValue }) } handleHeaderBulkValueInput = e => { this.setState({ headerBulkValue: e.target.value }) } handleHeaderBulkAddCancel = () => { this.setState({ headerBulkAddVisible: false }) } handleHeaderBulkAddConfirm = () => { const { headerBulkValue, req_headers } = this.state const headers = headerBulkValue .split(/[\r\n]+/g) .map(line => line.trim()) .filter(Boolean) .reduce((res, line) => { const [, key, value] = line.match(/^([^: ]+)\s*[: ]\s*(\S*)$/) || [] if (key != null && value != null) { const found = res.find(item => item.name === key) if (found) { found.value = value } else { res.push({ name: key, value: value, required: '1' }) } } return res }, (req_headers || []).slice()) this.setState({ headerBulkAddVisible: false, req_headers: headers }) } render() { const { getFieldDecorator } = this.props.form const { custom_field, projectMsg } = this.props const formItemLayout = { labelCol: { span: 4 }, wrapperCol: { span: 18 } } const res_body_use_schema_editor = checkIsJsonSchema(this.state.res_body) || '' const req_body_other_use_schema_editor = checkIsJsonSchema(this.state.req_body_other) || '' const queryTpl = (data, index) => { return ( {getFieldDecorator('req_query[' + index + '].name', { initialValue: data.name })()} {getFieldDecorator('req_query[' + index + '].type', { initialValue: data.type })( , )} {getFieldDecorator('req_query[' + index + '].required', { initialValue: data.required })( , )} {getFieldDecorator('req_query[' + index + '].example', { initialValue: data.example })(