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 (