mirror of
https://github.com/YMFE/yapi.git
synced 2025-04-18 15:20:25 +08:00
opti: 重构接口请求和测试部分代码
This commit is contained in:
parent
32bd14a2f5
commit
dbd56db77b
@ -25,7 +25,7 @@ const roleAction = {
|
||||
function isJson(json){
|
||||
if(!json) return false;
|
||||
try{
|
||||
json = json5.parse(json);
|
||||
json = JSON.parse(json);
|
||||
return json;
|
||||
}catch(e){
|
||||
return false;
|
||||
@ -34,6 +34,24 @@ function isJson(json){
|
||||
|
||||
exports.isJson = isJson;
|
||||
|
||||
function isJson5(json){
|
||||
if(!json) return false;
|
||||
try{
|
||||
json = json5.parse(json);
|
||||
return json;
|
||||
}catch(e){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function deepCopyJson(json){
|
||||
return JSON.parse(JSON.stringify(json));
|
||||
}
|
||||
|
||||
exports.deepCopyJson = deepCopyJson;
|
||||
|
||||
exports.isJson5 = isJson5;
|
||||
|
||||
exports.checkAuth = (action, role)=>{
|
||||
return Roles[roleAction[action]] <= Roles[role];
|
||||
}
|
||||
@ -305,4 +323,8 @@ exports.joinPath = (domain, joinPath) =>{
|
||||
joinPath = joinPath.substr(1);
|
||||
}
|
||||
return domain + joinPath;
|
||||
}
|
||||
|
||||
exports.safeArray = (arr) => {
|
||||
return Array.isArray(arr) ? arr : [];
|
||||
}
|
67
client/components/AceEditor/AceEditor.js
Normal file
67
client/components/AceEditor/AceEditor.js
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
import mockEditor from './mockEditor'
|
||||
import PropTypes from 'prop-types'
|
||||
import './AceEditor.scss'
|
||||
|
||||
const ModeMap = {
|
||||
'javascript' : 'ace/mode/javascript',
|
||||
'json' : 'ace/mode/json',
|
||||
'text' : 'ace/mode/text',
|
||||
'xml' : 'ace/mode/xml',
|
||||
'html' : 'ace/mode/html'
|
||||
}
|
||||
|
||||
function getMode(mode){
|
||||
return ModeMap[mode] || ModeMap.text
|
||||
}
|
||||
|
||||
class AceEditor extends React.PureComponent {
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
data: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
mode: PropTypes.string, //enum[json, text, javascript], default is javascript
|
||||
readOnly: PropTypes.bool,
|
||||
callback: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
fullScreen: PropTypes.bool
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.editor = mockEditor({
|
||||
container: this.editorElement,
|
||||
data: this.props.data,
|
||||
onChange: this.props.onChange,
|
||||
readOnly: this.props.readOnly,
|
||||
fullScreen: this.props.fullScreen
|
||||
})
|
||||
|
||||
let mode = this.props.mode || 'javascript';
|
||||
this.editor.editor.getSession().setMode(getMode(mode));
|
||||
if(typeof this.props.callback === 'function'){
|
||||
this.props.callback(this.editor.editor)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps){
|
||||
if(!this.editor) return;
|
||||
if(nextProps.data !== this.props.data && this.editor.getValue() !== nextProps.data){
|
||||
this.editor.setValue(nextProps.data);
|
||||
let mode = nextProps.mode || 'javascript';
|
||||
this.editor.editor.getSession().setMode(getMode(mode));
|
||||
this.editor.editor.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className={this.props.className} style={this.props.style || {width: '100%', height: '200px'}} ref={editor=>{
|
||||
this.editorElement=editor
|
||||
}} ></div>
|
||||
}
|
||||
}
|
||||
|
||||
export default AceEditor;
|
16
client/components/AceEditor/AceEditor.scss
Normal file
16
client/components/AceEditor/AceEditor.scss
Normal file
@ -0,0 +1,16 @@
|
||||
.ace_editor.fullScreen {
|
||||
height: auto;
|
||||
width: auto;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
position: fixed !important;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000008;
|
||||
}
|
||||
|
||||
.fullScreen {
|
||||
overflow: hidden
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
var ace = require('brace'),
|
||||
Mock = require('mockjs')
|
||||
Mock = require('mockjs');
|
||||
require('brace/mode/javascript');
|
||||
require('brace/mode/json');
|
||||
require('brace/mode/xml');
|
||||
require('brace/mode/html')
|
||||
require('brace/theme/xcode');
|
||||
require("brace/ext/language_tools.js");
|
||||
var json5 = require('json5');
|
||||
@ -57,6 +60,20 @@ var langTools = ace.acequire("ace/ext/language_tools"),
|
||||
{ name: '协议', mock: '@protocol' }
|
||||
];
|
||||
|
||||
let dom = ace.acequire("ace/lib/dom");
|
||||
ace.acequire("ace/commands/default_commands").commands.push({
|
||||
name: "Toggle Fullscreen",
|
||||
bindKey: "F9",
|
||||
exec: function(editor) {
|
||||
if(editor._fullscreen_yapi){
|
||||
let fullScreen = dom.toggleCssClass(document.body, "fullScreen")
|
||||
dom.setCssClass(editor.container, "fullScreen", fullScreen)
|
||||
editor.setAutoScrollEditorIntoView(!fullScreen)
|
||||
editor.resize()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function run(options) {
|
||||
var editor,
|
||||
mockEditor,
|
||||
@ -81,6 +98,7 @@ function run(options) {
|
||||
}
|
||||
data = options.data || '';
|
||||
options.readOnly = options.readOnly || false;
|
||||
options.fullScreen = options.fullScreen || false;
|
||||
|
||||
editor = ace.edit(container)
|
||||
editor.$blockScrolling = Infinity;
|
||||
@ -96,9 +114,10 @@ function run(options) {
|
||||
enableLiveAutocompletion: true,
|
||||
useWorker: true
|
||||
});
|
||||
editor._fullscreen_yapi = options.fullScreen;
|
||||
mockEditor = {
|
||||
curData: {},
|
||||
getValue: editor.getValue,
|
||||
getValue: ()=>mockEditor.curData.text,
|
||||
setValue: function (data) {
|
||||
data = data || '';
|
||||
if (typeof data === 'string') {
|
||||
@ -107,8 +126,12 @@ function run(options) {
|
||||
editor.setValue(JSON.stringify(data, null, " "))
|
||||
}
|
||||
},
|
||||
editor: editor
|
||||
editor: editor,
|
||||
options: options
|
||||
}
|
||||
|
||||
|
||||
|
||||
rhymeCompleter = {
|
||||
identifierRegexps: [/[@]/],
|
||||
getCompletions: function (editor, session, pos, prefix, callback) {
|
||||
@ -133,6 +156,7 @@ function run(options) {
|
||||
if (typeof options.onChange === 'function') {
|
||||
options.onChange.call(mockEditor, mockEditor.curData);
|
||||
}
|
||||
editor.clearSelection();
|
||||
|
||||
});
|
||||
|
59
client/components/Postman/CheckCrossInstall.js
Normal file
59
client/components/Postman/CheckCrossInstall.js
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import { Alert } from 'antd'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
exports.initCrossRequest = function initCrossRequest(fn){
|
||||
let startTime = 0;
|
||||
let _crossRequest = setInterval(() => {
|
||||
startTime += 500;
|
||||
if (startTime > 5000) {
|
||||
clearInterval(_crossRequest);
|
||||
}
|
||||
if (window.crossRequest) {
|
||||
clearInterval(_crossRequest);
|
||||
fn(true)
|
||||
} else {
|
||||
fn(false)
|
||||
}
|
||||
}, 500)
|
||||
return _crossRequest;
|
||||
}
|
||||
|
||||
CheckCrossInstall.propTypes = {
|
||||
hasPlugin: PropTypes.bool
|
||||
}
|
||||
|
||||
function CheckCrossInstall (props) {
|
||||
const hasPlugin = props.hasPlugin;
|
||||
return <div className={hasPlugin ? null : 'has-plugin'} >
|
||||
{hasPlugin ? '' : <Alert
|
||||
message={
|
||||
<div>
|
||||
重要:当前的接口测试服务,需安装免费测试增强插件,仅支持 chrome 浏览器,选择下面任意一种安装方式:
|
||||
<div>
|
||||
<a
|
||||
target="blank"
|
||||
href="https://chrome.google.com/webstore/detail/cross-request/cmnlfmgbjmaciiopcgodlhpiklaghbok?hl=en-US"
|
||||
>[Google 商店获取(需翻墙]
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
target="blank"
|
||||
href="/api/interface/download_crx"
|
||||
> [手动下载] </a>
|
||||
<span> zip 文件解压后将 crx 文件拖入到 chrome://extensions/ </span>
|
||||
<a
|
||||
target="blank"
|
||||
href="http://www.jianshu.com/p/12ca04c61fc6"
|
||||
> [详细安装教程] </a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
type="warning"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default CheckCrossInstall
|
866
client/components/Postman/Postman.1.js
Executable file
866
client/components/Postman/Postman.1.js
Executable file
@ -0,0 +1,866 @@
|
||||
import React, { PureComponent as Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Mock from 'mockjs'
|
||||
import { Button, Input, Checkbox, Select, Alert, Spin, Icon, Collapse, Tooltip, message, Switch } from 'antd'
|
||||
import { autobind } from 'core-decorators';
|
||||
import constants from '../../constants/variable.js'
|
||||
import mockEditor from '../../containers/Project/Interface/InterfaceList/mockEditor'
|
||||
import URL from 'url';
|
||||
const MockExtra = require('common/mock-extra.js')
|
||||
import './Postman.scss';
|
||||
import json5 from 'json5'
|
||||
import { isJson, handleJson, handleParamsValue,joinPath } from '../../common.js'
|
||||
import _ from "underscore"
|
||||
import ModalPostman from '../ModalPostman/index.js'
|
||||
|
||||
function json_parse(data) {
|
||||
try {
|
||||
return json5.parse(data)
|
||||
} catch (e) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
function isJsonData(headers) {
|
||||
if (!headers || typeof headers !== 'object') return false;
|
||||
let isResJson = false;
|
||||
Object.keys(headers).map(key => {
|
||||
if (/content-type/i.test(key) && /application\/json/i.test(headers[key])) {
|
||||
isResJson = true;
|
||||
}
|
||||
})
|
||||
return isResJson;
|
||||
}
|
||||
|
||||
const InputGroup = Input.Group;
|
||||
const Option = Select.Option;
|
||||
const Panel = Collapse.Panel;
|
||||
|
||||
const HTTP_METHOD = constants.HTTP_METHOD;
|
||||
|
||||
export default class Run extends Component {
|
||||
|
||||
static propTypes = {
|
||||
data: PropTypes.object,
|
||||
save: PropTypes.func,
|
||||
saveTip: PropTypes.string,
|
||||
type: PropTypes.string
|
||||
}
|
||||
|
||||
state = {
|
||||
res: null,
|
||||
resHeader: null,
|
||||
method: 'GET',
|
||||
domains: [],
|
||||
pathname: '',
|
||||
query: [],
|
||||
bodyForm: [],
|
||||
headers: [],
|
||||
caseEnv: '',
|
||||
bodyType: '',
|
||||
bodyOther: '',
|
||||
loading: false,
|
||||
validRes: [],
|
||||
hasPlugin: true,
|
||||
test_status: null,
|
||||
resMockTest: true,
|
||||
resStatusCode: null,
|
||||
resStatusText: '',
|
||||
modalVisible: false,
|
||||
inputIndex: 0,
|
||||
inputValue: '',
|
||||
modalType: ''
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
let startTime = 0;
|
||||
this.interval = setInterval(() => {
|
||||
startTime += 500;
|
||||
if (startTime > 5000) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
if (window.crossRequest) {
|
||||
clearInterval(this.interval);
|
||||
this.setState({
|
||||
hasPlugin: true
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
hasPlugin: false
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
this.getInterfaceState()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.data._id !== this.props.data._id) {
|
||||
this.getInterfaceState(nextProps)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// console.log('router',this.props);
|
||||
const { bodyType } = this.state;
|
||||
if (bodyType && bodyType !== 'file' && bodyType !== 'form') {
|
||||
this.loadBodyEditor()
|
||||
}
|
||||
}
|
||||
|
||||
handleValue(val) {
|
||||
return handleParamsValue(val, {});
|
||||
}
|
||||
|
||||
@autobind
|
||||
getInterfaceState(nextProps) {
|
||||
const props = nextProps || this.props;
|
||||
const { data, type } = props;
|
||||
const {
|
||||
method = '',
|
||||
path: url = '',
|
||||
req_headers = [],
|
||||
req_body_type,
|
||||
req_query = [],
|
||||
req_params = [],
|
||||
req_body_other = '',
|
||||
req_body_form = [],
|
||||
basepath = '',
|
||||
env = [],
|
||||
case_env = '',
|
||||
test_status = '',
|
||||
test_res_body = '',
|
||||
test_res_header = '',
|
||||
mock_verify = false
|
||||
} = data;
|
||||
|
||||
// case 任意编辑 pathname,不管项目的 basepath
|
||||
const pathname = (type === 'inter' ? (basepath + url) : url).replace(/\/+/g, '/');
|
||||
|
||||
// let hasContentType = false;
|
||||
// req_headers.forEach(headerItem => {
|
||||
// // TODO 'Content-Type' 排除大小写不同格式影响
|
||||
// if (headerItem.name === 'Content-Type'){
|
||||
// hasContentType = true;
|
||||
// headerItem.value = headerItem.value || 'application/x-www-form-urlencoded';
|
||||
// }
|
||||
// })
|
||||
// if (!hasContentType) {
|
||||
// req_headers.push({name: 'Content-Type', value: 'application/x-www-form-urlencoded'});
|
||||
// }
|
||||
// const domains = env.concat();
|
||||
// if (domain && !env.find(item => item.domain === domain)) {
|
||||
// domains.push({name: 'default', domain})
|
||||
// }
|
||||
|
||||
this.setState({
|
||||
method,
|
||||
domains: env.concat(),
|
||||
pathParam: req_params.concat(),
|
||||
pathname,
|
||||
query: req_query.concat(),
|
||||
bodyForm: req_body_form.concat(),
|
||||
headers: req_headers.concat(),
|
||||
bodyOther: req_body_other,
|
||||
caseEnv: case_env || (env[0] && env[0].name),
|
||||
bodyType: req_body_type || 'form',
|
||||
loading: false,
|
||||
test_status: test_status,
|
||||
validRes: [],
|
||||
res: test_res_body,
|
||||
resHeader: test_res_header,
|
||||
resMockTest: mock_verify
|
||||
}, () => {
|
||||
if (req_body_type && req_body_type !== 'file' && req_body_type !== 'form') {
|
||||
this.loadBodyEditor()
|
||||
}
|
||||
if (test_res_body) {
|
||||
this.bindAceEditor();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
handleResponse = (res, header, third)=> {
|
||||
res = third.res.body || third.res.statusText;
|
||||
try {
|
||||
this.setState({
|
||||
loading: false,
|
||||
resStatusCode: third.res.status,
|
||||
resStatusText: third.res.statusText
|
||||
})
|
||||
res = json_parse(res);
|
||||
let test_status = 'error';
|
||||
let validRes = [];
|
||||
if (isNaN(third.res.status)) {
|
||||
res = res || '请求异常';
|
||||
message.error(res);
|
||||
test_status = 'error';
|
||||
|
||||
|
||||
} else {
|
||||
const { res_body, res_body_type } = this.props.data;
|
||||
let query = {};
|
||||
this.state.query.forEach(item => {
|
||||
query[item.name] = item.value;
|
||||
})
|
||||
let body = {};
|
||||
if (this.state.bodyType === 'form') {
|
||||
this.state.bodyForm.forEach(item => {
|
||||
body[item.name] = item.value;
|
||||
})
|
||||
} else if (this.state.bodyType === 'json') {
|
||||
body = json_parse(this.state.bodyOther);
|
||||
}
|
||||
if (res_body && res_body_type === 'json' && typeof res === 'object' && this.state.resMockTest === true) {
|
||||
let tpl = MockExtra(json_parse(res_body), {
|
||||
query: query,
|
||||
body: body
|
||||
})
|
||||
validRes = Mock.valid(tpl, res)
|
||||
}
|
||||
|
||||
|
||||
if (Array.isArray(validRes) && validRes.length > 0) {
|
||||
message.warn('请求完成, 返回数据跟接口定义不匹配');
|
||||
validRes = validRes.map(item => {
|
||||
return item.message
|
||||
})
|
||||
test_status = 'invalid';
|
||||
} else if (Array.isArray(validRes) && validRes.length === 0) {
|
||||
message.success('请求完成');
|
||||
test_status = 'ok'
|
||||
}
|
||||
}
|
||||
this.setState({ res, resHeader: header, validRes, test_status }, this.bindAceEditor)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@autobind
|
||||
reqRealInterface() {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false })
|
||||
return;
|
||||
}
|
||||
const { headers, bodyForm, pathParam, bodyOther, caseEnv, domains, method, pathname, query, bodyType } = this.state;
|
||||
|
||||
let path = pathname;
|
||||
|
||||
pathParam.forEach(item => {
|
||||
path = path.replace(`:${item.name}`, this.handleValue(item.value) || `:${item.name}`);
|
||||
});
|
||||
|
||||
let curdomain = _.find(domains, item => item.name === caseEnv).domain;
|
||||
const urlObj = URL.parse(joinPath(curdomain, path));
|
||||
|
||||
let pathQuery = {};
|
||||
urlObj.query && urlObj.query.split('&').forEach(item => {
|
||||
if (item) {
|
||||
item = item.split('=');
|
||||
pathQuery[item[0]] = item[1];
|
||||
}
|
||||
})
|
||||
|
||||
const href = URL.format({
|
||||
protocol: urlObj.protocol || 'http',
|
||||
host: urlObj.host,
|
||||
pathname: urlObj.pathname,
|
||||
query: Object.assign(pathQuery, this.getQueryObj(query))
|
||||
});
|
||||
let reqBody;
|
||||
if (bodyType === 'form') {
|
||||
reqBody = this.arrToObj(bodyForm)
|
||||
} else if(bodyType === 'json'){
|
||||
reqBody = isJson(bodyOther);
|
||||
if (reqBody === false) {
|
||||
return message.error('请求 Body 的 json 格式有误')
|
||||
} else {
|
||||
reqBody = handleJson(reqBody, this.handleValue)
|
||||
}
|
||||
}else{
|
||||
reqBody = bodyOther;
|
||||
}
|
||||
|
||||
this.setState({ loading: true })
|
||||
window.crossRequest({
|
||||
url: href,
|
||||
method,
|
||||
headers: this.getHeadersObj(headers),
|
||||
data: reqBody,
|
||||
files: bodyType === 'form' ? this.getFiles(bodyForm) : {},
|
||||
file: bodyType === 'file' ? 'single-file' : null,
|
||||
timeout: 8240000,
|
||||
success: this.handleResponse,
|
||||
error: this.handleResponse
|
||||
})
|
||||
}
|
||||
|
||||
// @autobind
|
||||
// changeDomain(value) {
|
||||
// this.setState({ currDomain: value });
|
||||
// }
|
||||
|
||||
@autobind
|
||||
selectDomain(value) {
|
||||
this.setState({ caseEnv: value });
|
||||
}
|
||||
|
||||
@autobind
|
||||
changeHeader(v, index, isName) {
|
||||
// v = v.target.value
|
||||
const headers = json_parse(JSON.stringify(this.state.headers));
|
||||
if (isName) {
|
||||
headers[index].name = v;
|
||||
} else {
|
||||
headers[index].value = v;
|
||||
}
|
||||
this.setState({ headers });
|
||||
}
|
||||
@autobind
|
||||
addHeader() {
|
||||
const { headers } = this.state;
|
||||
this.setState({ headers: headers.concat([{ name: '', value: '' }]) })
|
||||
}
|
||||
@autobind
|
||||
deleteHeader(index) {
|
||||
const { headers } = this.state;
|
||||
this.setState({ headers: headers.filter((item, i) => +index !== +i) });
|
||||
}
|
||||
@autobind
|
||||
setContentType() {
|
||||
// const headersObj = this.getHeadersObj(this.state.headers);
|
||||
// headersObj['Content-Type'] = type;
|
||||
// this.setState({ headers: this.objToArr(headersObj) })
|
||||
}
|
||||
|
||||
@autobind
|
||||
changeQuery(v, index, key) {
|
||||
console.log(v);
|
||||
key = key || 'value';
|
||||
// v = v.target.value;
|
||||
const query = json_parse(JSON.stringify(this.state.query));
|
||||
if (key == 'enable') {
|
||||
query[index].enable = v;
|
||||
} else {
|
||||
query[index].value = v;
|
||||
query[index].enable = true;
|
||||
}
|
||||
this.setState({ query });
|
||||
}
|
||||
@autobind
|
||||
addQuery() {
|
||||
const { query } = this.state;
|
||||
this.setState({ query: query.concat([{ name: '', value: '' }]) })
|
||||
}
|
||||
@autobind
|
||||
deleteQuery(index) {
|
||||
const { query } = this.state;
|
||||
this.setState({ query: query.filter((item, i) => +index !== +i) });
|
||||
}
|
||||
|
||||
@autobind
|
||||
changePathParam(v, index, isKey) {
|
||||
// v = v.target.value;
|
||||
const pathParam = JSON.parse(JSON.stringify(this.state.pathParam));
|
||||
const name = pathParam[index].name;
|
||||
let newPathname = this.state.pathname;
|
||||
if (isKey) {
|
||||
if (!name && v) {
|
||||
newPathname += `/:${v}`;
|
||||
} else {
|
||||
newPathname = newPathname.replace(`/:${name}`, v ? `/:${v}` : '')
|
||||
}
|
||||
pathParam[index].name = v;
|
||||
} else {
|
||||
|
||||
pathParam[index].value = v;
|
||||
}
|
||||
this.setState({ pathParam, pathname: newPathname });
|
||||
}
|
||||
@autobind
|
||||
addPathParam() {
|
||||
const { pathParam } = this.state;
|
||||
this.setState({ pathParam: pathParam.concat([{ name: '', value: '' }]) })
|
||||
}
|
||||
@autobind
|
||||
deletePathParam(index) {
|
||||
const { pathParam } = this.state;
|
||||
const name = pathParam[index].name;
|
||||
const newPathname = this.state.pathname.replace(`/:${name}`, '');
|
||||
this.setState({ pathParam: pathParam.filter((item, i) => +index !== +i), pathname: newPathname });
|
||||
}
|
||||
|
||||
@autobind
|
||||
changeBody(v, index, key) {
|
||||
const bodyForm = json_parse(JSON.stringify(this.state.bodyForm));
|
||||
key = key || 'value';
|
||||
if (key === 'value') {
|
||||
bodyForm[index].enable = true;
|
||||
if (bodyForm[index].type === 'file') {
|
||||
bodyForm[index].value = 'file_' + index
|
||||
} else {
|
||||
bodyForm[index].value = v
|
||||
}
|
||||
} else if (key === 'enable') {
|
||||
bodyForm[index].enable = v
|
||||
}
|
||||
|
||||
this.setState({ bodyForm });
|
||||
}
|
||||
@autobind
|
||||
addBody() {
|
||||
const { bodyForm } = this.state;
|
||||
this.setState({ bodyForm: bodyForm.concat([{ name: '', value: '', type: 'text' }]) })
|
||||
}
|
||||
@autobind
|
||||
deleteBody(index) {
|
||||
const { bodyForm } = this.state;
|
||||
this.setState({ bodyForm: bodyForm.filter((item, i) => +index !== +i) });
|
||||
}
|
||||
|
||||
@autobind
|
||||
changeMethod(value) {
|
||||
this.setState({ method: value });
|
||||
}
|
||||
|
||||
@autobind
|
||||
changePath(e) {
|
||||
const path = e.target.value;
|
||||
const urlObj = URL.parse(path, true);
|
||||
this.setState({
|
||||
query: this.objToArr(urlObj.query),
|
||||
pathname: urlObj.pathname
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
changeBodyType(value) {
|
||||
this.setState({ bodyType: value }, () => {
|
||||
if (value !== 'file' && value !== 'form') {
|
||||
this.loadBodyEditor()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// hasCrossRequestPlugin() {
|
||||
// const dom = document.getElementById('y-request');
|
||||
// return dom.getAttribute('key') === 'yapi';
|
||||
// }
|
||||
|
||||
objToArr(obj, key, value) {
|
||||
const keyName = key || 'name';
|
||||
const valueName = value || 'value';
|
||||
const arr = []
|
||||
Object.keys(obj).forEach((_key) => {
|
||||
if (_key) {
|
||||
arr.push({ [keyName]: _key, [valueName]: obj[_key] });
|
||||
}
|
||||
})
|
||||
return arr;
|
||||
}
|
||||
arrToObj(arr) {
|
||||
const obj = {};
|
||||
arr.forEach(item => {
|
||||
if (item)
|
||||
if (item.name && item.type !== 'file' && item.enable) {
|
||||
obj[item.name] = this.handleValue(item.value);
|
||||
}
|
||||
})
|
||||
return obj;
|
||||
}
|
||||
|
||||
getFiles(bodyForm) {
|
||||
const files = {};
|
||||
bodyForm.forEach(item => {
|
||||
if (item.name && item.enable === true && item.type === 'file') {
|
||||
files[item.name] = item.value
|
||||
}
|
||||
})
|
||||
return files;
|
||||
}
|
||||
getQueryObj(query) {
|
||||
const queryObj = {};
|
||||
query.forEach(item => {
|
||||
if (item.name && item.enable) {
|
||||
queryObj[item.name] = this.handleValue(item.value);
|
||||
}
|
||||
})
|
||||
return queryObj;
|
||||
}
|
||||
getHeadersObj(headers) {
|
||||
const headersObj = {};
|
||||
headers.forEach(item => {
|
||||
if (item.name && item.value) {
|
||||
headersObj[item.name] = this.handleValue(item.value);
|
||||
}
|
||||
})
|
||||
return headersObj;
|
||||
}
|
||||
|
||||
bindAceEditor = () => {
|
||||
mockEditor({
|
||||
container: 'res-body-pretty',
|
||||
data: this.state.res,
|
||||
readOnly: true,
|
||||
onChange: function () { }
|
||||
})
|
||||
|
||||
|
||||
mockEditor({
|
||||
container: 'res-headers-pretty',
|
||||
data: this.state.resHeader,
|
||||
readOnly: true,
|
||||
onChange: function () { }
|
||||
})
|
||||
}
|
||||
loadBodyEditor = () => {
|
||||
const that = this;
|
||||
setTimeout(function () {
|
||||
mockEditor({
|
||||
container: 'body-other-edit',
|
||||
data: that.state.bodyOther,
|
||||
onChange: function (d) {
|
||||
that.setState({
|
||||
bodyOther: d.text
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 0);
|
||||
}
|
||||
|
||||
|
||||
@autobind
|
||||
onTestSwitched(checked) {
|
||||
this.setState({
|
||||
resMockTest: checked
|
||||
});
|
||||
}
|
||||
|
||||
// 模态框的相关操作
|
||||
showModal = (val, index, type) => {
|
||||
this.setState({
|
||||
modalVisible: true,
|
||||
inputIndex: index,
|
||||
inputValue: val,
|
||||
modalType: type
|
||||
|
||||
});
|
||||
}
|
||||
handleOk = (val) => {
|
||||
const { inputIndex, modalType } = this.state
|
||||
switch (modalType) {
|
||||
case 'query':
|
||||
this.changeQuery(val, inputIndex);
|
||||
break;
|
||||
case 'body':
|
||||
this.changeBody(val, inputIndex);
|
||||
break;
|
||||
case 'header':
|
||||
this.changeHeader(val, inputIndex);
|
||||
break;
|
||||
case 'pathParam':
|
||||
this.changePathParam(val, inputIndex);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.setState({ modalVisible: false });
|
||||
}
|
||||
handleCancel = () => {
|
||||
this.setState({ modalVisible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { method, domains, pathParam, pathname, query, inputValue, headers, bodyForm, caseEnv, bodyType, resHeader, loading, validRes } = this.state;
|
||||
HTTP_METHOD[method] = HTTP_METHOD[method] || {}
|
||||
const hasPlugin = this.state.hasPlugin;
|
||||
let isResJson = isJsonData(resHeader);
|
||||
let path = pathname;
|
||||
pathParam.forEach(item => {
|
||||
let val = this.handleValue(item.value);
|
||||
path = path.replace(`:${item.name}`, val || `:${item.name}`);
|
||||
});
|
||||
const pathObj = URL.parse(path);
|
||||
path = pathObj.pathname;
|
||||
let pathQuery = {};
|
||||
pathObj.query && pathObj.query.split('&').forEach(item => {
|
||||
if (item) {
|
||||
item = item.split('=');
|
||||
pathQuery[item[0]] = item[1];
|
||||
}
|
||||
})
|
||||
const search = decodeURIComponent(URL.format({ query: Object.assign(pathQuery, this.getQueryObj(query)) }));
|
||||
|
||||
let validResView;
|
||||
validResView = validRes.map((item, index) => {
|
||||
return <div key={index}>{item}</div>
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="interface-test postman">
|
||||
<ModalPostman
|
||||
visible={this.state.modalVisible}
|
||||
handleCancel={this.handleCancel}
|
||||
handleOk={this.handleOk}
|
||||
inputValue={inputValue}
|
||||
envType={this.props.type}
|
||||
>
|
||||
</ModalPostman>
|
||||
<div className={hasPlugin ? null : 'has-plugin'} >
|
||||
{hasPlugin ? '' : <Alert
|
||||
message={
|
||||
<div>
|
||||
{/* 温馨提示:当前正在使用接口测试服务,请安装我们为您免费提供的测试增强插件 (该插件可支持任何 chrome 内核的浏览器) */}
|
||||
重要:当前的接口测试服务,需安装免费测试增强插件 (支持所有 webkit 内核),选择下面任意一种安装方式:
|
||||
<div>
|
||||
<a
|
||||
target="blank"
|
||||
href="https://chrome.google.com/webstore/detail/cross-request/cmnlfmgbjmaciiopcgodlhpiklaghbok?hl=en-US"
|
||||
> [Google 商店获取(需翻墙)]</a>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
target="blank"
|
||||
href="/api/interface/download_crx"
|
||||
> [手动下载] </a>
|
||||
<span> zip 文件解压后将 crx 文件拖入到 chrome://extensions/ </span>
|
||||
<a
|
||||
target="blank"
|
||||
href="http://www.jianshu.com/p/12ca04c61fc6"
|
||||
> [详细安装教程] </a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
type="warning"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
<h2 className="interface-title" style={{ marginTop: 0 }}>请求部分
|
||||
<Tooltip placement="top" title="在 '设置->环境配置' 配置 domain"><Icon type="question-circle-o" /></Tooltip>
|
||||
</h2>
|
||||
<div className="url">
|
||||
|
||||
<InputGroup compact style={{ display: 'flex' }}>
|
||||
<Select disabled value={method} style={{ flexBasis: 60 }} onChange={this.changeMethod} >
|
||||
<Option value="GET">GET</Option>
|
||||
<Option value="POST">POST</Option>
|
||||
</Select>
|
||||
|
||||
<Select value={caseEnv} style={{ flexBasis: 180, flexGrow: 1 }} onSelect={this.selectDomain}>
|
||||
{
|
||||
domains.map((item, index) => (<Option value={item.name} key={index}>{item.name + ':' + item.domain}</Option>))
|
||||
}
|
||||
</Select>
|
||||
|
||||
<Input disabled value={path + search} onChange={this.changePath} spellCheck="false" style={{ flexBasis: 180, flexGrow: 1 }} />
|
||||
</InputGroup>
|
||||
|
||||
<Tooltip placement="bottom" title={(() => {
|
||||
if (hasPlugin) {
|
||||
return '发送请求'
|
||||
} else {
|
||||
return '请安装cross-request插件'
|
||||
}
|
||||
})()}>
|
||||
<Button
|
||||
disabled={!hasPlugin}
|
||||
onClick={this.reqRealInterface}
|
||||
type="primary"
|
||||
style={{ marginLeft: 10 }}
|
||||
icon={loading ? 'loading' : ''}
|
||||
>{loading ? '取消' : '发送'}</Button>
|
||||
</Tooltip>
|
||||
<Tooltip placement="bottom" title={this.props.saveTip}>
|
||||
<Button
|
||||
onClick={this.props.save}
|
||||
type="primary"
|
||||
style={{ marginLeft: 10 }}
|
||||
>{this.props.type === 'inter' ? '保存' : '保存'}</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Collapse defaultActiveKey={['0', '1', '2', '3']} bordered={true}>
|
||||
<Panel header="PATH PARAMETERS" key="0" className={pathParam.length === 0 ? 'hidden' : ''}>
|
||||
{
|
||||
pathParam.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className="key-value-wrap">
|
||||
<Input disabled value={item.name} onChange={e => this.changePathParam(e.target.value, index, true)} className="key" />
|
||||
<span className="eq-symbol">=</span>
|
||||
<Input
|
||||
value={item.value}
|
||||
className="value"
|
||||
onChange={e => this.changePathParam(e.target.value, index)}
|
||||
placeholder="参数值"
|
||||
addonAfter={<Icon type="edit" onClick={() => this.showModal(item.value, index, 'pathParam')} />}
|
||||
/>
|
||||
<Icon style={{ display: 'none' }} type="delete" className="icon-btn" onClick={() => this.deletePathParam(index)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
<Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addPathParam}>添加Path参数</Button>
|
||||
</Panel>
|
||||
<Panel header="QUERY PARAMETERS" key="1" className={query.length === 0 ? 'hidden' : ''}>
|
||||
{
|
||||
query.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className="key-value-wrap">
|
||||
<Input disabled value={item.name} className="key" />
|
||||
|
||||
{item.required == 1 ?
|
||||
<Checkbox checked={true} disabled >enable</Checkbox> :
|
||||
<Checkbox checked={item.enable} onChange={e => this.changeQuery(e.target.checked, index, 'enable')}>enable</Checkbox>
|
||||
}
|
||||
<span className="eq-symbol">=</span>
|
||||
<Input
|
||||
value={item.value}
|
||||
className="value"
|
||||
onChange={e => this.changeQuery(e.target.value, index)}
|
||||
placeholder="参数值"
|
||||
addonAfter={<Icon type="edit" onClick={() => this.showModal(item.value, index, 'query')} />}
|
||||
/>
|
||||
|
||||
<Icon style={{ display: 'none' }} type="delete" className="icon-btn" onClick={() => this.deleteQuery(index)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
<Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addQuery}>添加Query参数</Button>
|
||||
</Panel>
|
||||
<Panel header="HEADERS" key="2" className={headers.length === 0 ? 'hidden' : ''}>
|
||||
{
|
||||
headers.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className="key-value-wrap">
|
||||
<Input disabled value={item.name} onChange={e => this.changeHeader(e.target.value, index, true)} className="key" />
|
||||
<span className="eq-symbol">=</span>
|
||||
<Input
|
||||
value={item.value}
|
||||
className="value"
|
||||
onChange={e => this.changeHeader(e.target.value, index)}
|
||||
placeholder="参数值"
|
||||
addonAfter={<Icon type="edit" onClick={() => this.showModal(item.value, index, 'header')} />}
|
||||
/>
|
||||
<Icon style={{ display: 'none' }} type="delete" className="icon-btn" onClick={() => this.deleteHeader(index)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
<Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addHeader}>添加Header</Button>
|
||||
</Panel>
|
||||
<Panel
|
||||
header={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>BODY</div>
|
||||
</div>
|
||||
}
|
||||
key="3"
|
||||
className={HTTP_METHOD[method].request_body ? 'POST' : 'hidden'}
|
||||
>
|
||||
|
||||
<div style={{ display: HTTP_METHOD[method].request_body && bodyType !== 'form' && bodyType !== 'file' ? 'block' : 'none' }}>
|
||||
<div id="body-other-edit" style={{ marginTop: 10, minHeight: 150 }} className="pretty-editor"></div>
|
||||
</div>
|
||||
|
||||
{
|
||||
HTTP_METHOD[method].request_body && bodyType === 'form' &&
|
||||
<div>
|
||||
{
|
||||
bodyForm.map((item, index) => {
|
||||
return (
|
||||
<div key={index} className="key-value-wrap">
|
||||
<Input disabled value={item.name} onChange={e => this.changeBody(e.target.value, index, 'key')} className="key" />
|
||||
|
||||
{item.required == 1 ?
|
||||
<Checkbox checked={true} disabled >enable</Checkbox> :
|
||||
<Checkbox checked={item.enable} onChange={e => this.changeBody(e.target.checked, index, 'enable')}>enable</Checkbox>
|
||||
}
|
||||
<span className="eq-symbol">=</span>
|
||||
{item.type === 'file' ?
|
||||
<Input type="file" id={'file_' + index} onChange={e => this.changeBody(e.target.value, index, 'value')} multiple className="value" /> :
|
||||
<Input
|
||||
value={item.value}
|
||||
className="value"
|
||||
onChange={e => this.changeBody(e.target.value, index)}
|
||||
placeholder="参数值"
|
||||
addonAfter={<Icon type="edit" onClick={() => this.showModal(item.value, index, 'body')} />}
|
||||
/>
|
||||
|
||||
}
|
||||
<Icon style={{ display: 'none' }} type="delete" className="icon-btn" onClick={() => this.deleteBody(index)} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
<Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addBody}>添加Form参数</Button>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
HTTP_METHOD[method].request_body && bodyType === 'file' &&
|
||||
<div>
|
||||
<Input type="file" id="single-file"></Input>
|
||||
</div>
|
||||
}
|
||||
{/*
|
||||
method !== 'POST' &&
|
||||
<div>GET 请求没有 BODY。</div>
|
||||
*/}
|
||||
</Panel>
|
||||
</Collapse>
|
||||
|
||||
<h2 className="interface-title">返回结果</h2>
|
||||
|
||||
<Spin spinning={this.state.loading}>
|
||||
<h2 style={{ display: this.state.resStatusCode ? '' : 'none' }} className={'res-code ' + ((this.state.resStatusCode >= 200 && this.state.resStatusCode < 400 && !this.state.loading) ? 'success' : 'fail')}>
|
||||
{this.state.resStatusCode + ' ' + this.state.resStatusText}</h2>
|
||||
|
||||
<div style={{ display: this.state.res ? '' : 'none' }} className="container-header-body">
|
||||
<div className="header">
|
||||
<div className="container-title">
|
||||
<h4>Headers</h4>
|
||||
</div>
|
||||
<div id="res-headers-pretty" className="pretty-editor-header"></div>
|
||||
</div>
|
||||
<div className="resizer">
|
||||
<div className="container-title">
|
||||
<h4 style={{ visibility: 'hidden' }}>1</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="body">
|
||||
<div className="container-title">
|
||||
<h4>Body</h4>
|
||||
</div>
|
||||
<div id="res-body-pretty" className="pretty-editor-body" style={{ display: isResJson ? '' : 'none' }}></div>
|
||||
<div
|
||||
style={{ display: isResJson ? 'none' : '' }}
|
||||
className="res-body-text"
|
||||
>{this.state.res && this.state.res.toString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
|
||||
<p style={{ display: this.state.resStatusCode === null ? '' : 'none' }}>发送请求后在这里查看返回结果。</p>
|
||||
|
||||
<h2 className="interface-title">数据结构验证
|
||||
<Switch style={{ verticalAlign: 'text-bottom', marginLeft: '8px' }} checked={this.state.resMockTest} onChange={this.onTestSwitched} />
|
||||
</h2>
|
||||
<div className={(isResJson && this.state.resMockTest) ? '' : 'none'}>
|
||||
{(isResJson && this.state.resMockTest) ? validResView : <div><p>若开启此功能,则发送请求后在这里查看验证结果。</p><p>数据结构验证在接口编辑页面配置,YApi 将根据 Response body 验证请求返回的结果。</p></div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
@import '../../styles/mixin.scss';
|
||||
.postman {
|
||||
.pretty-editor {
|
||||
border: 1px solid #d9d9d9;
|
||||
@ -11,42 +12,137 @@
|
||||
.pretty-editor-header {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
min-height: 200px;
|
||||
min-height: 300px;
|
||||
}
|
||||
}
|
||||
.interface-test {
|
||||
padding: .24rem;
|
||||
.ant-checkbox-wrapper{
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// .modal-postman {
|
||||
// .ant-modal-body {
|
||||
// padding: 0;
|
||||
// }
|
||||
// .ant-modal-footer {
|
||||
// background-color: #f5f5f5;
|
||||
// }
|
||||
// .modal-postman-form {
|
||||
// padding: 16px;
|
||||
// max-height: 300px;
|
||||
// overflow-y: scroll;
|
||||
// .row {
|
||||
// margin-bottom: 8px;
|
||||
// }
|
||||
// }
|
||||
// .modal-postman-expression, .modal-postman-preview {
|
||||
// border-top: 1px solid #e9e9e9;
|
||||
// padding: 8px 16px;
|
||||
// }
|
||||
// .modal-postman-expression {
|
||||
// }
|
||||
// .modal-postman-preview {
|
||||
// background-color: #f5f5f5;
|
||||
// }
|
||||
// .title {
|
||||
// border-left: 3px solid #2395f1;
|
||||
// padding-left: 8px;
|
||||
// }
|
||||
// }
|
||||
.interface-test {
|
||||
padding: .24rem;
|
||||
.ant-checkbox-wrapper{
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.response-tab{
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
.ant-tabs-nav{
|
||||
background: #f7f7f7;
|
||||
border-radius: 0 0 4px 4px;
|
||||
border: 1px solid #d9d9d9;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header, .body{
|
||||
padding-left: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.response-test{
|
||||
min-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-spin-blur {
|
||||
.res-code.success {
|
||||
background-color: transparent;
|
||||
}
|
||||
.res-code.fail {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.res-code {
|
||||
padding: .08rem .28rem;
|
||||
color: #fff;
|
||||
margin-left: -.1rem;
|
||||
margin-right: -.28rem;
|
||||
transition: all .2s;
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.res-code.success {
|
||||
background-color: $color-antd-green;
|
||||
}
|
||||
.res-code.fail {
|
||||
background-color: $color-antd-red;
|
||||
}
|
||||
|
||||
// 容器:左侧是header 右侧是body
|
||||
.container-header-body {
|
||||
display: flex;
|
||||
padding-bottom: .36rem;
|
||||
.header, .body {
|
||||
flex: 1 0 300px;
|
||||
.pretty-editor-header, .pretty-editor-body {
|
||||
height: 100%;
|
||||
}
|
||||
.postman .pretty-editor-body {
|
||||
min-height: 200px;
|
||||
}
|
||||
.ace_print-margin {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
max-width: 400px;
|
||||
}
|
||||
.container-title {
|
||||
padding: .08rem 0;
|
||||
}
|
||||
.resizer {
|
||||
flex: 0 0 21px;
|
||||
position: relative;
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: #acaaaa;
|
||||
opacity: .8;
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
}
|
||||
}
|
||||
// res body 无返回json时显示text信息
|
||||
.res-body-text {
|
||||
height: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.has-plugin, .req-part, .resp-part {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.url {
|
||||
display: flex;
|
||||
margin: 24px 10px;
|
||||
}
|
||||
.key-value-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 0 5px 0;
|
||||
.key {
|
||||
flex-basis: 220px;
|
||||
}
|
||||
.value {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.eq-symbol {
|
||||
margin: 0 5px;
|
||||
}
|
||||
.params-enable{
|
||||
width: 98px;
|
||||
}
|
||||
}
|
||||
.icon-btn {
|
||||
cursor: pointer;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.icon-btn:hover {
|
||||
color: #2395f1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
163
client/components/Postman/postmanLib.js
Normal file
163
client/components/Postman/postmanLib.js
Normal file
@ -0,0 +1,163 @@
|
||||
import { isJson5, handleJson, joinPath, safeArray } from '../../common.js'
|
||||
import constants from '../../constants/variable.js'
|
||||
import _ from "underscore"
|
||||
import URL from 'url';
|
||||
const HTTP_METHOD = constants.HTTP_METHOD;
|
||||
|
||||
exports.checkRequestBodyIsRaw = checkRequestBodyIsRaw;
|
||||
exports.handleParams = handleParams;
|
||||
exports.handleContentType = handleContentType;
|
||||
exports.crossRequest = crossRequest;
|
||||
|
||||
const ContentTypeMap = {
|
||||
'application/json': 'json',
|
||||
'application/xml': 'xml',
|
||||
'other': 'text',
|
||||
'application/html': 'html'
|
||||
}
|
||||
|
||||
function handleContentType(headers) {
|
||||
if (!headers || typeof headers !== 'object') return ContentTypeMap.other;
|
||||
let contentTypeItem = 'other';
|
||||
try {
|
||||
Object.keys(headers).forEach(key => {
|
||||
if (/content-type/i.test(key)) {
|
||||
contentTypeItem = headers[key].split(";")[0].trim().toLowerCase();
|
||||
}
|
||||
})
|
||||
return ContentTypeMap[contentTypeItem] ? ContentTypeMap[contentTypeItem] : ContentTypeMap.other;
|
||||
} catch (err) {
|
||||
return ContentTypeMap.other
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function checkRequestBodyIsRaw(method, reqBodyType) {
|
||||
if (reqBodyType && reqBodyType !== 'file' && reqBodyType !== 'form' && HTTP_METHOD[method].request_body) {
|
||||
return reqBodyType;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function handleCurrDomain(domains, case_env) {
|
||||
let currDomain = _.find(domains, item => item.name === case_env);
|
||||
if (!currDomain) {
|
||||
currDomain = domains[0];
|
||||
}
|
||||
return currDomain;
|
||||
}
|
||||
|
||||
function crossRequest(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.error = options.success = function (res, header, data) {
|
||||
|
||||
if (isNaN(data.res.status)) {
|
||||
reject({
|
||||
body: res,
|
||||
header,
|
||||
message: '请求异常,请检查 chrome network 错误信息...'
|
||||
})
|
||||
}
|
||||
resolve(data);
|
||||
}
|
||||
window.crossRequest(options);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function handleParams(interfaceData, handleValue, requestParams) {
|
||||
function paramsToObjectWithEnable(arr) {
|
||||
const obj = {};
|
||||
safeArray(arr).forEach(item => {
|
||||
if (item && item.name && (item.enable || item.required === '1')) {
|
||||
obj[item.name] = handleValue(item.value);
|
||||
if (requestParams) {
|
||||
requestParams[item.name] = obj[item.name];
|
||||
}
|
||||
}
|
||||
})
|
||||
return obj;
|
||||
}
|
||||
|
||||
function paramsToObjectUnWithEnable(arr) {
|
||||
const obj = {};
|
||||
safeArray(arr).forEach(item => {
|
||||
if (item && item.name) {
|
||||
obj[item.name] = handleValue(item.value);
|
||||
if (requestParams) {
|
||||
requestParams[item.name] = obj[item.name];
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
return obj;
|
||||
}
|
||||
|
||||
let { case_env, path, env } = interfaceData;
|
||||
let pathQuery = {}, currDomain, requestBody, requestOptions;
|
||||
|
||||
interfaceData.req_params = interfaceData.req_params || [];
|
||||
interfaceData.req_params.forEach(item => {
|
||||
let val = handleValue(item.value);
|
||||
if (requestParams) {
|
||||
requestParams[item.name] = val;
|
||||
}
|
||||
|
||||
path = path.replace(`:${item.name}`, val || `:${item.name}`);
|
||||
});
|
||||
|
||||
|
||||
currDomain = handleCurrDomain(env, case_env);
|
||||
const urlObj = URL.parse(joinPath(currDomain.domain, path));
|
||||
urlObj.query && urlObj.query.split('&').forEach(item => {
|
||||
if (item) {
|
||||
item = item.split('=');
|
||||
pathQuery[item[0]] = item[1];
|
||||
}
|
||||
})
|
||||
|
||||
const url = URL.format({
|
||||
protocol: urlObj.protocol || 'http',
|
||||
host: urlObj.host,
|
||||
pathname: urlObj.pathname,
|
||||
query: Object.assign(pathQuery, paramsToObjectWithEnable(interfaceData.req_query))
|
||||
|
||||
});
|
||||
|
||||
if (HTTP_METHOD[interfaceData.method].request_body) {
|
||||
if (interfaceData.req_body_type === 'form') {
|
||||
requestBody = paramsToObjectWithEnable(safeArray(interfaceData.req_body_form).filter(item => {
|
||||
return item.type == 'text'
|
||||
}));
|
||||
} else {
|
||||
let reqBody = isJson5(interfaceData.req_body_other);
|
||||
if (reqBody === false) {
|
||||
requestBody = handleValue(reqBody);
|
||||
} else {
|
||||
if (requestParams) {
|
||||
requestParams = Object.assign(requestParams, reqBody);
|
||||
}
|
||||
requestBody = handleJson(reqBody, handleValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
requestOptions = {
|
||||
url,
|
||||
method: interfaceData.method,
|
||||
headers: paramsToObjectUnWithEnable(interfaceData.req_headers),
|
||||
data: requestBody,
|
||||
timeout: 82400000
|
||||
}
|
||||
|
||||
if (interfaceData.req_body_type === 'form') {
|
||||
requestOptions.files = paramsToObjectWithEnable(safeArray(interfaceData.req_body_form).filter(item => {
|
||||
return item.type == 'file'
|
||||
}))
|
||||
} else if (interfaceData.req_body_type === 'file') {
|
||||
requestOptions.file = 'single-file'
|
||||
}
|
||||
return requestOptions;
|
||||
|
||||
}
|
@ -3,7 +3,6 @@ import Footer from './Footer/Footer.js'
|
||||
import Header from './Header/Header.js'
|
||||
import Intro from './Intro/Intro.js'
|
||||
import Loading from './Loading/Loading.js'
|
||||
import MockDoc from './MockDoc/MockDoc.js'
|
||||
import ProjectCard from './ProjectCard/ProjectCard.js'
|
||||
import Subnav from './Subnav/Subnav.js'
|
||||
import Postman from './Postman/Postman'
|
||||
@ -14,7 +13,6 @@ export {
|
||||
Header,
|
||||
Intro,
|
||||
Loading,
|
||||
MockDoc,
|
||||
ProjectCard,
|
||||
Subnav,
|
||||
Postman
|
||||
|
@ -99,16 +99,17 @@ export default class InterfaceCaseContent extends Component {
|
||||
updateCase = async () => {
|
||||
|
||||
const {
|
||||
caseEnv: case_env,
|
||||
pathname: path,
|
||||
method,
|
||||
pathParam: req_params,
|
||||
query: req_query,
|
||||
headers: req_headers,
|
||||
bodyType: req_body_type,
|
||||
bodyForm: req_body_form,
|
||||
bodyOther: req_body_other,
|
||||
resMockTest: mock_verify
|
||||
case_env,
|
||||
req_params,
|
||||
req_query,
|
||||
req_headers,
|
||||
req_body_type,
|
||||
req_body_form,
|
||||
req_body_other,
|
||||
test_script,
|
||||
enable_script,
|
||||
test_res_body,
|
||||
test_res_header
|
||||
} = this.postman.state;
|
||||
|
||||
const { editCasename: casename } = this.state;
|
||||
@ -116,27 +117,19 @@ export default class InterfaceCaseContent extends Component {
|
||||
let params = {
|
||||
id,
|
||||
casename,
|
||||
case_env,
|
||||
path,
|
||||
method,
|
||||
case_env,
|
||||
req_params,
|
||||
req_query,
|
||||
req_headers,
|
||||
req_body_type,
|
||||
req_body_form,
|
||||
req_body_other,
|
||||
mock_verify
|
||||
test_script,
|
||||
enable_script,
|
||||
test_res_body,
|
||||
test_res_header
|
||||
|
||||
};
|
||||
if (this.postman.state.test_status !== 'error') {
|
||||
params.test_res_body = this.postman.state.res;
|
||||
params.test_status = this.postman.state.test_status;
|
||||
params.test_res_header = this.postman.state.resHeader;
|
||||
}
|
||||
|
||||
|
||||
if (params.test_res_body && typeof params.test_res_body === 'object') {
|
||||
params.test_res_body = JSON.stringify(params.test_res_body, null, ' ');
|
||||
}
|
||||
|
||||
const res = await axios.post('/api/col/up_case', params);
|
||||
if (this.props.currCase.casename !== casename) {
|
||||
@ -166,7 +159,7 @@ export default class InterfaceCaseContent extends Component {
|
||||
render() {
|
||||
const { currCase, currProject } = this.props;
|
||||
const { isEditingCasename, editCasename } = this.state;
|
||||
const data = Object.assign({}, currCase, currProject, { _id: currCase._id });
|
||||
const data = Object.assign({}, currCase, {env: currProject.env}, { _id: currCase._id });
|
||||
return (
|
||||
<div style={{ padding: '6px 0' }} className="case-content">
|
||||
<div className="case-title">
|
||||
@ -182,7 +175,9 @@ export default class InterfaceCaseContent extends Component {
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Postman data={data} type="case" saveTip="更新保存修改" save={this.updateCase} ref={this.savePostmanRef} />
|
||||
{Object.keys(currCase).length > 0 &&
|
||||
<Postman data={data} type="case" saveTip="更新保存修改" save={this.updateCase} ref={this.savePostmanRef} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -8,27 +8,19 @@ import { Tooltip, Icon, Button, Spin, Modal, message, Select, Switch } from 'ant
|
||||
import { fetchInterfaceColList, fetchCaseList, setColData } from '../../../../reducer/modules/interfaceCol'
|
||||
import HTML5Backend from 'react-dnd-html5-backend';
|
||||
import { DragDropContext } from 'react-dnd';
|
||||
import { isJson, handleJson, handleParamsValue, joinPath } from '../../../../common.js'
|
||||
import mockEditor from '../InterfaceList/mockEditor';
|
||||
import { isJson, handleParamsValue } from '../../../../common.js'
|
||||
import AceEditor from 'client/components/AceEditor/AceEditor';
|
||||
import * as Table from 'reactabular-table';
|
||||
import * as dnd from 'reactabular-dnd';
|
||||
import * as resolve from 'table-resolver';
|
||||
import axios from 'axios'
|
||||
import URL from 'url';
|
||||
import Mock from 'mockjs'
|
||||
import json5 from 'json5'
|
||||
import CaseReport from './CaseReport.js'
|
||||
import _ from 'underscore'
|
||||
import { handleParams, crossRequest } from 'client/components/Postman/postmanLib.js'
|
||||
import { initCrossRequest } from 'client/components/Postman/CheckCrossInstall.js'
|
||||
|
||||
const MockExtra = require('common/mock-extra.js')
|
||||
const Option = Select.Option;
|
||||
function json_parse(data) {
|
||||
try {
|
||||
return json5.parse(data)
|
||||
} catch (e) {
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleReport(json) {
|
||||
try {
|
||||
@ -113,28 +105,16 @@ class InterfaceColContent extends Component {
|
||||
this.handleColdata(this.props.currCaseList)
|
||||
}
|
||||
|
||||
let startTime = 0;
|
||||
this.interval = setInterval(() => {
|
||||
startTime += 500;
|
||||
if (startTime > 5000) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
if (window.crossRequest) {
|
||||
clearInterval(this.interval);
|
||||
this.setState({
|
||||
hasPlugin: true
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
hasPlugin: false
|
||||
})
|
||||
}
|
||||
}, 500)
|
||||
this._crossRequestInterval = initCrossRequest((hasPlugin) => {
|
||||
this.setState({
|
||||
hasPlugin: hasPlugin
|
||||
})
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.interval)
|
||||
clearInterval(this._crossRequestInterval)
|
||||
}
|
||||
|
||||
handleColdata = (rows) => {
|
||||
@ -154,7 +134,7 @@ class InterfaceColContent extends Component {
|
||||
executeTests = async () => {
|
||||
for (let i = 0, l = this.state.rows.length, newRows, curitem; i < l; i++) {
|
||||
let { rows } = this.state;
|
||||
curitem = Object.assign({}, rows[i], { test_status: 'loading' });
|
||||
curitem = Object.assign({}, rows[i],{env: this.props.currProject.env}, { test_status: 'loading' });
|
||||
newRows = [].concat([], rows);
|
||||
newRows[i] = curitem;
|
||||
this.setState({
|
||||
@ -193,92 +173,44 @@ class InterfaceColContent extends Component {
|
||||
}
|
||||
|
||||
handleTest = async (interfaceData) => {
|
||||
const { currProject } = this.props;
|
||||
let requestParams = {};
|
||||
let { case_env } = interfaceData;
|
||||
let path = interfaceData.path;
|
||||
interfaceData.req_params = interfaceData.req_params || [];
|
||||
interfaceData.req_params.forEach(item => {
|
||||
let val = this.handleValue(item.value);
|
||||
requestParams[item.name] = val;
|
||||
path = path.replace(`:${item.name}`, val || `:${item.name}`);
|
||||
});
|
||||
const domains = currProject.env.concat();
|
||||
let options = handleParams(interfaceData, this.handleValue, requestParams)
|
||||
|
||||
case_env = this.state.currColEnv ? this.state.currColEnv : case_env;
|
||||
let result = { code: 400,
|
||||
msg: '数据异常',
|
||||
validRes: [],
|
||||
...options
|
||||
};
|
||||
|
||||
let pathQuery = {};
|
||||
let currDomain = _.find(domains, item => item.name === case_env);
|
||||
|
||||
if (!currDomain) {
|
||||
currDomain = domains[0];
|
||||
}
|
||||
|
||||
const urlObj = URL.parse(joinPath(currDomain.domain, path));
|
||||
urlObj.query && urlObj.query.split('&').forEach(item => {
|
||||
if (item) {
|
||||
item = item.split('=');
|
||||
pathQuery[item[0]] = item[1];
|
||||
}
|
||||
})
|
||||
|
||||
const href = URL.format({
|
||||
protocol: urlObj.protocol || 'http',
|
||||
host: urlObj.host,
|
||||
pathname: urlObj.pathname,
|
||||
query: Object.assign(pathQuery, this.getQueryObj(interfaceData.req_query, requestParams))
|
||||
});
|
||||
|
||||
let result = { code: 400, msg: '数据异常', validRes: [] };
|
||||
let that = this;
|
||||
|
||||
result.url = href;
|
||||
result.method = interfaceData.method;
|
||||
result.headers = that.getHeadersObj(interfaceData.req_headers);
|
||||
if (interfaceData.req_body_type === 'form') {
|
||||
result.body = that.arrToObj(interfaceData.req_body_form, requestParams)
|
||||
} else {
|
||||
let reqBody = isJson(interfaceData.req_body_other);
|
||||
if (reqBody === false) {
|
||||
result.body = this.handleValue(interfaceData.req_body_other)
|
||||
} else {
|
||||
reqBody = handleJson(reqBody, this.handleValue)
|
||||
requestParams = Object.assign(requestParams, reqBody);
|
||||
result.body = JSON.stringify(reqBody)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
let data = await this.crossRequest({
|
||||
url: href,
|
||||
method: interfaceData.method,
|
||||
headers: that.getHeadersObj(interfaceData.req_headers),
|
||||
data: result.body,
|
||||
timeout: 8240000
|
||||
})
|
||||
let res = data.res.body = json_parse(data.res.body);
|
||||
let header = data.res.header;
|
||||
result.res_header = header;
|
||||
result.res_body = res;
|
||||
result.params = requestParams;
|
||||
let validRes = [];
|
||||
if (res && typeof res === 'object') {
|
||||
if (interfaceData.mock_verify) {
|
||||
let tpl = MockExtra(json_parse(interfaceData.res_body), {
|
||||
query: interfaceData.req_query,
|
||||
body: interfaceData.req_body_form
|
||||
})
|
||||
validRes = Mock.valid(tpl, res);
|
||||
}
|
||||
let data = await crossRequest(options)
|
||||
let res = data.res.body = isJson(data.res.body);
|
||||
|
||||
result = {
|
||||
...result,
|
||||
res_header: data.res.header,
|
||||
res_body: res
|
||||
}
|
||||
|
||||
let validRes = [];
|
||||
// 弃用 mock 字段验证功能
|
||||
// if (res && typeof res === 'object') {
|
||||
// if (interfaceData.mock_verify) {
|
||||
// let tpl = MockExtra(json_parse(interfaceData.res_body), {
|
||||
// query: interfaceData.req_query,
|
||||
// body: interfaceData.req_body_form
|
||||
// })
|
||||
// validRes = Mock.valid(tpl, res);
|
||||
// }
|
||||
// }
|
||||
let responseData = Object.assign({}, {
|
||||
status: data.res.status,
|
||||
body: res,
|
||||
header: data.res.header,
|
||||
statusText: data.res.statusText
|
||||
})
|
||||
await that.handleScriptTest(interfaceData, responseData, validRes, requestParams);
|
||||
await this.handleScriptTest(interfaceData, responseData, validRes, requestParams);
|
||||
if (validRes.length === 0) {
|
||||
result.code = 0;
|
||||
result.validRes = [{ message: '验证通过' }];
|
||||
@ -286,41 +218,23 @@ class InterfaceColContent extends Component {
|
||||
result.code = 1;
|
||||
result.validRes = validRes;
|
||||
}
|
||||
return result;
|
||||
|
||||
|
||||
} catch (data) {
|
||||
if (data.err) {
|
||||
data.err = data.err || '请求异常';
|
||||
try {
|
||||
data.err = json_parse(data.err);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
result.res_body = data.err;
|
||||
result.res_header = data.header;
|
||||
} else {
|
||||
result.res_body = data.message;
|
||||
result = {
|
||||
...result,
|
||||
res_header: data.header,
|
||||
res_body: data.err || data.message,
|
||||
status: null,
|
||||
statusText: data.message,
|
||||
code: 400
|
||||
}
|
||||
|
||||
result.code = 400;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
crossRequest = (options) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
options.error = options.success = function (res, header, data) {
|
||||
|
||||
if(isNaN(data.res.status)){
|
||||
reject({
|
||||
err: res,
|
||||
header
|
||||
})
|
||||
}
|
||||
resolve(data);
|
||||
}
|
||||
window.crossRequest(options);
|
||||
})
|
||||
result.params = requestParams;
|
||||
return result;
|
||||
}
|
||||
|
||||
//response, validRes
|
||||
@ -450,21 +364,12 @@ class InterfaceColContent extends Component {
|
||||
curScript: findCase.test_script,
|
||||
advVisible: true,
|
||||
curCaseid: id
|
||||
}, () => {
|
||||
let that = this;
|
||||
if (that.Editor) {
|
||||
that.Editor.setValue(this.state.curScript);
|
||||
} else {
|
||||
that.Editor = mockEditor({
|
||||
container: 'case-script',
|
||||
data: this.state.curScript,
|
||||
onChange: function (d) {
|
||||
that.setState({
|
||||
curScript: d.text
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleScriptChange = (d)=>{
|
||||
this.setState({
|
||||
curScript: d.text
|
||||
})
|
||||
}
|
||||
|
||||
@ -700,7 +605,8 @@ class InterfaceColContent extends Component {
|
||||
是否开启:
|
||||
<Switch checked={this.state.enableScript} onChange={e => this.setState({ enableScript: e })} />
|
||||
</h3>
|
||||
<div className="case-script" id="case-script" style={{ minHeight: 500 }}></div>
|
||||
<AceEditor className="case-script" data={this.state.curScript} onChange={this.handleScriptChange} />
|
||||
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
|
@ -124,5 +124,6 @@
|
||||
}
|
||||
|
||||
.case-script{
|
||||
min-height: 500px;
|
||||
margin: 10px
|
||||
}
|
@ -88,70 +88,3 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
// 容器:左侧是header 右侧是body
|
||||
.container-header-body {
|
||||
display: flex;
|
||||
padding-bottom: .36rem;
|
||||
.header, .body {
|
||||
flex: 1 0 300px;
|
||||
.pretty-editor-header, .pretty-editor-body {
|
||||
height: 100%;
|
||||
}
|
||||
.postman .pretty-editor-body {
|
||||
min-height: 200px;
|
||||
}
|
||||
.ace_print-margin {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.header {
|
||||
max-width: 400px;
|
||||
}
|
||||
.container-title {
|
||||
padding: .08rem 0;
|
||||
}
|
||||
.resizer {
|
||||
flex: 0 0 21px;
|
||||
position: relative;
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: $color-text-dark;
|
||||
opacity: .8;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
// res body 无返回json时显示text信息
|
||||
.res-body-text {
|
||||
height: 100%;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
.ant-spin-blur {
|
||||
.res-code.success {
|
||||
background-color: transparent;
|
||||
}
|
||||
.res-code.fail {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
.res-code {
|
||||
padding: .08rem .28rem;
|
||||
color: #fff;
|
||||
margin-left: -.28rem;
|
||||
margin-right: -.28rem;
|
||||
transition: all .2s;
|
||||
position: relative;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.res-code.success {
|
||||
background-color: $color-antd-green;
|
||||
}
|
||||
.res-code.fail {
|
||||
background-color: $color-antd-red;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import json5 from 'json5'
|
||||
import { message, Tabs, Affix } from 'antd'
|
||||
import Editor from 'wangeditor'
|
||||
import EasyDragSort from '../../../../components/EasyDragSort/EasyDragSort.js'
|
||||
import mockEditor from 'client/components/AceEditor/mockEditor';
|
||||
|
||||
const TabPane = Tabs.TabPane;
|
||||
let EditFormContext;
|
||||
@ -50,7 +51,7 @@ const dataTpl = {
|
||||
req_body_form: { name: "", type: "text", required: "1", desc: "", example: "" }
|
||||
}
|
||||
|
||||
const mockEditor = require('./mockEditor.js');
|
||||
|
||||
const HTTP_METHOD = constants.HTTP_METHOD;
|
||||
const HTTP_METHOD_KEYS = Object.keys(HTTP_METHOD);
|
||||
const HTTP_REQUEST_HEADER = constants.HTTP_REQUEST_HEADER;
|
||||
@ -249,7 +250,8 @@ class InterfaceEditForm extends Component {
|
||||
req_body_other: d.text
|
||||
})
|
||||
EditFormContext.props.changeEditStatus(initReqBody !== d.text);
|
||||
}
|
||||
},
|
||||
fullScreen: true
|
||||
})
|
||||
|
||||
this.resBodyEditor = mockEditor({
|
||||
@ -260,7 +262,8 @@ class InterfaceEditForm extends Component {
|
||||
res_body: d.text
|
||||
});
|
||||
EditFormContext.props.changeEditStatus(initResBody !== d.text);
|
||||
}
|
||||
},
|
||||
fullScreen: true
|
||||
})
|
||||
|
||||
this.mockPreview = mockEditor({
|
||||
@ -771,11 +774,12 @@ class InterfaceEditForm extends Component {
|
||||
|
||||
<Row className={'interface-edit-item ' + (this.props.form.getFieldValue('req_body_type') === 'json' ? this.state.hideTabs.req.body : 'hide')}>
|
||||
<Col className="interface-edit-json-info">
|
||||
基于 Json5, 使用注释方式写参数说明 <Tooltip title={<pre>
|
||||
基于 Json5, 参数描述信息用注释的方式实现 <Tooltip title={<pre>
|
||||
{Json5Example}
|
||||
</pre>}>
|
||||
<Icon type="question-circle-o" style={{ color: "#086dbf" }} />
|
||||
</Tooltip>
|
||||
,“全局编辑” 或 “退出全屏” 请按 F9
|
||||
</Col>
|
||||
<Col id="req_body_json" style={{ minHeight: "300px" }}>
|
||||
</Col>
|
||||
@ -836,7 +840,9 @@ class InterfaceEditForm extends Component {
|
||||
{Json5Example}
|
||||
</pre>}>
|
||||
<Icon type="question-circle-o" style={{ color: "#086dbf" }} />
|
||||
</Tooltip> ,具体使用方法请 <span className="href" onClick={() => window.open('http://yapi.qunar.com/mock.html', '_blank')}>查看文档</span></h3>
|
||||
</Tooltip> ,具体使用方法请 <span className="href" onClick={() => window.open('http://yapi.qunar.com/mock.html', '_blank')}>查看文档</span>
|
||||
,“全局编辑” 或 “退出全屏” 请按 F9
|
||||
</h3>
|
||||
<div id="res_body_json" style={{ minHeight: "300px", display: this.state.jsonType === 'tpl' ? 'block' : 'none' }} ></div>
|
||||
<div id="mock-preview" style={{ backgroundColor: "#eee", lineHeight: "20px", minHeight: "300px", display: this.state.jsonType === 'preview' ? 'block' : 'none' }}></div>
|
||||
</div>
|
||||
|
@ -49,17 +49,13 @@ export default class Run extends Component {
|
||||
const project_id = this.props.match.params.id;
|
||||
const interface_id = this.props.currInterface._id;
|
||||
const {
|
||||
caseEnv: case_env,
|
||||
pathname: path,
|
||||
method,
|
||||
pathParam: req_params,
|
||||
query: req_query,
|
||||
headers: req_headers,
|
||||
bodyType: req_body_type,
|
||||
bodyForm: req_body_form,
|
||||
bodyOther: req_body_other,
|
||||
resMockTest: mock_verify
|
||||
|
||||
case_env,
|
||||
req_params,
|
||||
req_query,
|
||||
req_headers,
|
||||
req_body_type,
|
||||
req_body_form,
|
||||
req_body_other
|
||||
} = this.postman.state;
|
||||
|
||||
let params = {
|
||||
@ -68,24 +64,14 @@ export default class Run extends Component {
|
||||
col_id: colId,
|
||||
project_id,
|
||||
case_env,
|
||||
path,
|
||||
method,
|
||||
req_params,
|
||||
req_query,
|
||||
req_headers,
|
||||
req_body_type,
|
||||
req_body_form,
|
||||
req_body_other,
|
||||
mock_verify
|
||||
req_body_other
|
||||
};
|
||||
|
||||
if(this.postman.state.test_status !== 'error'){
|
||||
params.test_res_body = this.postman.state.res;
|
||||
params.test_report = this.postman.state.validRes;
|
||||
params.test_status = this.postman.state.test_status;
|
||||
params.test_res_header = this.postman.state.resHeader;
|
||||
}
|
||||
|
||||
if(params.test_res_body && typeof params.test_res_body === 'object'){
|
||||
params.test_res_body = JSON.stringify(params.test_res_body, null, ' ');
|
||||
}
|
||||
@ -101,7 +87,10 @@ export default class Run extends Component {
|
||||
|
||||
render () {
|
||||
const { currInterface, currProject } = this.props;
|
||||
const data = Object.assign({}, currInterface, currProject, {_id: currInterface._id})
|
||||
const data = Object.assign({}, currInterface, {
|
||||
env: currProject.env
|
||||
})
|
||||
data.path = currProject.basepath + currInterface.path;
|
||||
return (
|
||||
<div>
|
||||
<Postman data={data} type="inter" saveTip="保存到集合" save={() => this.setState({saveCaseModalVisible: true})} ref={this.savePostmanRef} />
|
||||
|
@ -1,33 +1,4 @@
|
||||
.interface-test {
|
||||
.has-plugin, .req-part, .resp-part {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.url {
|
||||
display: flex;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.key-value-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 0 5px 0;
|
||||
.key {
|
||||
flex-basis: 220px;
|
||||
}
|
||||
.value {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.eq-symbol {
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
.icon-btn {
|
||||
cursor: pointer;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.icon-btn:hover {
|
||||
color: #2395f1;
|
||||
}
|
||||
}
|
||||
|
||||
.add-col-modal {
|
||||
.col-list {
|
||||
height: 200px;
|
||||
@ -45,4 +16,4 @@
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,15 +4,14 @@ import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Table, Icon, Row, Col } from 'antd'
|
||||
import { Link } from 'react-router-dom'
|
||||
const mockEditor = require('./mockEditor.js')
|
||||
import AceEditor from 'client/components/AceEditor/AceEditor';
|
||||
import { formatTime } from '../../../../common.js';
|
||||
import ErrMsg from '../../../../components/ErrMsg/ErrMsg.js';
|
||||
import variable from '../../../../constants/variable';
|
||||
import constants from '../../../../constants/variable.js'
|
||||
|
||||
const HTTP_METHOD = constants.HTTP_METHOD;
|
||||
// import { Card } from 'antd'
|
||||
// import { getMockUrl } from '../../reducer/modules/news.js'
|
||||
|
||||
|
||||
@connect(state => {
|
||||
return {
|
||||
@ -89,34 +88,21 @@ class View extends Component {
|
||||
}
|
||||
|
||||
return <div style={{ display: dataSource.length ? "" : "none" }} className="colBody">
|
||||
<h3 className="col-title">Body:</h3>
|
||||
<Table bordered size="small" pagination={false} columns={columns} dataSource={dataSource} />
|
||||
</div>
|
||||
|
||||
} else if (req_body_type === 'file') {
|
||||
|
||||
return <div style={{ display: this.props.curData.req_body_other ? "" : "none" }} className="colBody">
|
||||
<h3 className="col-title">Body:</h3>
|
||||
<div>{this.props.curData.req_body_other}</div>
|
||||
</div>
|
||||
|
||||
} else if (req_body_type === 'raw') {
|
||||
|
||||
return <div style={{ display: this.props.curData.req_body_other ? "" : "none" }} className="colBody">
|
||||
<h3 className="col-title">Body:</h3>
|
||||
<div>{this.props.curData.req_body_other}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
res_body(res_body_type, res_body) {
|
||||
if (res_body_type === 'json') {
|
||||
let h = this.countEnter(this.props.curData.res_body);
|
||||
let h = this.countEnter(res_body);
|
||||
return <div className="colBody">
|
||||
<div id="vres_body_json" style={{ minHeight: h * 16 + 100 }}></div>
|
||||
{/* <div id="vres_body_json" style={{ minHeight: h * 16 + 100 }}></div> */}
|
||||
<AceEditor data={res_body} readOnly={true} style={{ minHeight: h * 16 + 100 }} />
|
||||
</div>
|
||||
} else if (res_body_type === 'raw') {
|
||||
return <div className="colBody">
|
||||
<div>{res_body}</div>
|
||||
<AceEditor data={res_body} readOnly={true} mode="text" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -174,37 +160,13 @@ class View extends Component {
|
||||
}
|
||||
return c;
|
||||
}
|
||||
bindAceEditor() {
|
||||
if (this.props.curData.req_body_type === "json" && this.props.curData.title) {
|
||||
mockEditor({
|
||||
container: 'vreq_body_json',
|
||||
data: this.props.curData.req_body_other,
|
||||
readOnly: true,
|
||||
onChange: function () { }
|
||||
})
|
||||
}
|
||||
if (this.props.curData.title && this.props.curData.res_body_type === "json") {
|
||||
let content = this.props.curData.res_body ? this.props.curData.res_body: '没有定义';
|
||||
mockEditor({
|
||||
container: 'vres_body_json',
|
||||
data: content,
|
||||
readOnly: true,
|
||||
onChange: function () { }
|
||||
})
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
|
||||
if (this.props.curData.title) {
|
||||
this.bindAceEditor.bind(this)();
|
||||
}
|
||||
componentDidMount() {
|
||||
if (!this.props.curData.title && this.state.init) {
|
||||
this.setState({ init: false });
|
||||
}
|
||||
}
|
||||
componentDidUpdate() {
|
||||
this.bindAceEditor.bind(this)();
|
||||
}
|
||||
|
||||
componentWillUpdate() {
|
||||
if (!this.props.curData.title && this.state.init) {
|
||||
this.setState({ init: false });
|
||||
@ -296,9 +258,8 @@ class View extends Component {
|
||||
let methodColor = variable.METHOD_COLOR[this.props.curData.method ? this.props.curData.method.toLowerCase() : "get"];
|
||||
// statusColor = statusColor[this.props.curData.status?this.props.curData.status.toLowerCase():"undone"];
|
||||
let h = this.countEnter(this.props.curData.req_body_other);
|
||||
const aceEditor = <div style={{ display: this.props.curData.req_body_other && this.props.curData.req_body_type === "json" ? "block" : "none" }} className="colBody">
|
||||
<span className="colKey">请求Body:</span>
|
||||
<div id="vreq_body_json" style={{ minHeight: h * 16 + 20 }}></div>
|
||||
const aceEditor = <div style={{ display: this.props.curData.req_body_other && (this.props.curData.req_body_type !== "form" ) ? "block" : "none" }} className="colBody">
|
||||
<AceEditor data={this.props.curData.req_body_other} style={{ minHeight: h * 16 + 20 }} mode={this.props.curData.req_body_type === 'json' ? 'javascript' : 'text'} />
|
||||
</div>
|
||||
if (!methodColor) methodColor = "get";
|
||||
let res = <div className="caseContainer">
|
||||
@ -352,20 +313,15 @@ class View extends Component {
|
||||
<h3 className="col-title">Query:</h3>
|
||||
{this.req_query(this.props.curData.req_query)}
|
||||
</div> : ""}
|
||||
{/*<div className="colreqBodyType">
|
||||
<span className="colKey">请求Body类型:</span>
|
||||
<span className="colValue">{this.props.curData.req_body_type}</span>
|
||||
</div>*/}
|
||||
|
||||
<div style={{display: this.props.curData.method && HTTP_METHOD[this.props.curData.method.toUpperCase()].request_body ? '' : 'none'}}>
|
||||
<h3 className="col-title">Body:</h3>
|
||||
{ aceEditor }
|
||||
{
|
||||
this.req_body_form(this.props.curData.req_body_type, this.props.curData.req_body_form)
|
||||
}
|
||||
</div>
|
||||
{/*<div className="colreqBodyType">
|
||||
<span className="colKey">返回Body类型:</span>
|
||||
<span className="colValue">{this.props.curData.res_body_type}</span>
|
||||
</div>*/}
|
||||
|
||||
<h2 className="interface-title">Response</h2>
|
||||
{this.res_body(this.props.curData.res_body_type, this.props.curData.res_body)}
|
||||
</div>;
|
||||
|
@ -5,7 +5,7 @@ import PropTypes from 'prop-types'
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Form, Switch, Button, message, Icon, Tooltip, Radio } from 'antd';
|
||||
import MockCol from './MockCol/MockCol.js'
|
||||
import mockEditor from 'client/containers/Project/Interface/InterfaceList/mockEditor';
|
||||
import mockEditor from 'client/components/AceEditor/mockEditor';
|
||||
import constants from '../../client/constants/variable.js'
|
||||
const FormItem = Form.Item;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Button, Form, Input, Switch, Select, Icon,message, Modal, Col, Row, InputNumber, AutoComplete } from 'antd';
|
||||
import { safeAssign } from '../../../client/common.js';
|
||||
import mockEditor from '../../../client/containers/Project/Interface/InterfaceList/mockEditor';
|
||||
import constants from '../../../client/constants/variable.js'
|
||||
import { safeAssign } from 'client/common.js';
|
||||
import mockEditor from 'client/components/AceEditor/mockEditor';
|
||||
import constants from 'client/constants/variable.js'
|
||||
import { httpCodes } from '../index.js'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
|
@ -24,7 +24,7 @@ class interfaceModel extends baseModel {
|
||||
path: String,
|
||||
params: [{
|
||||
name: String, value: String
|
||||
}],
|
||||
}]
|
||||
},
|
||||
req_query: [{
|
||||
name: String, value: String, example: String, desc: String, required: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user