Merge branch 'dev' of gitlab.corp.qunar.com:mfe/yapi into dev

This commit is contained in:
suxiaoxin 2017-09-26 13:51:50 +08:00
commit 6039e2d0c9
8 changed files with 220 additions and 205 deletions

View File

@ -175,12 +175,12 @@ export default class HeaderCom extends Component {
return ( return (
<Header className="header-box m-header"> <Header className="header-box m-header">
<div className="content g-row"> <div className="content g-row">
<div className="logo"> <Link onClick={this.relieveLink} to="/group" className="logo">
<Link to="/group" onClick={this.relieveLink} className="href"> <div className="href">
<span className="img">{logoSVG('32px')}</span> <span className="img">{logoSVG('32px')}</span>
{/*<span className="logo-name">YApi</span>*/} {/*<span className="logo-name">YApi</span>*/}
</Link> </div>
</div> </Link>
<Breadcrumb /> <Breadcrumb />
<div className="user-toolbar"> <div className="user-toolbar">
{login? {login?

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Mock from 'mockjs' import Mock from 'mockjs'
import { Button, Input, Select, Card, Alert, Spin, Icon, Collapse, Tooltip, message, AutoComplete } from 'antd' import { Button, Input, Select, Alert, Spin, Icon, Collapse, Tooltip, message, AutoComplete } from 'antd'
import { autobind } from 'core-decorators'; import { autobind } from 'core-decorators';
import constants from '../../constants/variable.js' import constants from '../../constants/variable.js'
@ -517,7 +517,7 @@ export default class Run extends Component {
return ( return (
<div className="interface-test postman"> <div className="interface-test postman">
<div className="has-plugin"> <div className={ hasPlugin? null : 'has-plugin' } >
{hasPlugin ? '' : <Alert {hasPlugin ? '' : <Alert
message={ message={
<div> <div>
@ -547,204 +547,205 @@ export default class Run extends Component {
} }
</div> </div>
<Card title={<Tooltip placement="top" title="在 '设置->环境配置' 配置 domain">请求部分&nbsp;<Icon type="question-circle-o" /></Tooltip>} noHovering className="req-part">
<div className="url">
<InputGroup compact style={{ display: 'flex' }}> <h2 className="interface-title" style={{ marginTop: 0 }}>请求部分&nbsp;
<Select disabled value={method} style={{ flexBasis: 60 }} onChange={this.changeMethod} > <Tooltip placement="top" title="在 '设置->环境配置' 配置 domain"><Icon type="question-circle-o" /></Tooltip>
<Option value="GET">GET</Option> </h2>
<Option value="POST">POST</Option> <div className="url">
</Select>
<Select value={caseEnv} style={{ flexBasis: 180, flexGrow: 1 }} onSelect={this.selectDomain}> <InputGroup compact style={{ display: 'flex' }}>
{ <Select disabled value={method} style={{ flexBasis: 60 }} onChange={this.changeMethod} >
domains.map((item, index) => (<Option value={item.name} key={index}>{item.name + '' + item.domain}</Option>)) <Option value="GET">GET</Option>
} <Option value="POST">POST</Option>
</Select> </Select>
<Input disabled value={path + search} onChange={this.changePath} spellCheck="false" style={{ flexBasis: 180, flexGrow: 1 }} /> <Select value={caseEnv} style={{ flexBasis: 180, flexGrow: 1 }} onSelect={this.selectDomain}>
</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) => { domains.map((item, index) => (<Option value={item.name} key={index}>{item.name + '' + item.domain}</Option>))
return (
<div key={index} className="key-value-wrap">
<Input disabled value={item.name} onChange={e => this.changePathParam(e, index, true)} className="key" />
<span className="eq-symbol">=</span>
<AutoComplete
value={item.value}
onChange={e => this.changePathParam(e, index)}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
<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> </Select>
</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} onChange={e => this.changeQuery(e, index, true)} className="key" />
<span className="eq-symbol">=</span>
<AutoComplete
value={item.value}
onChange={e => this.changeQuery(e, index)}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
<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, index, true)} className="key" />
<span className="eq-symbol">=</span>
<AutoComplete
value={item.value}
onChange={e => this.changeHeader(e, index)}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
<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' }}> <Input disabled value={path + search} onChange={this.changePath} spellCheck="false" style={{ flexBasis: 180, flexGrow: 1 }} />
<div id="body-other-edit" style={{ marginTop: 10, minHeight: 150 }} className="pretty-editor"></div> </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, index, true)} className="key" />
<span className="eq-symbol">=</span>
<AutoComplete
value={item.value}
onChange={e => this.changePathParam(e, index)}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
<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} onChange={e => this.changeQuery(e, index, true)} className="key" />
<span className="eq-symbol">=</span>
<AutoComplete
value={item.value}
onChange={e => this.changeQuery(e, index)}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
<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, index, true)} className="key" />
<span className="eq-symbol">=</span>
<AutoComplete
value={item.value}
onChange={e => this.changeHeader(e, index)}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
<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> </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' }}>
HTTP_METHOD[method].request_body && bodyType === 'form' && <div id="body-other-edit" style={{ marginTop: 10, minHeight: 150 }} className="pretty-editor"></div>
<div> </div>
{
bodyForm.map((item, index) => {
return (
<div key={index} className="key-value-wrap">
<Input disabled value={item.name} onChange={e => this.changeBody(e, index, 'key')} className="key" />
<span>[</span>
<Select disabled value={item.type} onChange={e => this.changeBody(e, index, 'type')}>
<Option value="file">File</Option>
<Option value="text">Text</Option>
</Select>
<span>]</span>
<span className="eq-symbol">=</span>
{item.type === 'file' ?
<Input type="file" id={'file_' + index} onChange={e => this.changeBody(e, index, 'value')} multiple className="value" /> :
<AutoComplete
value={item.value}
onChange={e => this.changeBody(e, index, 'value')}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
} {
<Icon style={{ display: 'none' }} type="delete" className="icon-btn" onClick={() => this.deleteBody(index)} /> HTTP_METHOD[method].request_body && bodyType === 'form' &&
</div> <div>
) {
}) bodyForm.map((item, index) => {
} return (
<Button style={{ display: 'none' }} type="primary" icon="plus" onClick={this.addBody}>添加Form参数</Button> <div key={index} className="key-value-wrap">
</div> <Input disabled value={item.name} onChange={e => this.changeBody(e, index, 'key')} className="key" />
} <span>[</span>
{ <Select disabled value={item.type} onChange={e => this.changeBody(e, index, 'type')}>
HTTP_METHOD[method].request_body && bodyType === 'file' && <Option value="file">File</Option>
<div> <Option value="text">Text</Option>
<Input type="file" id="single-file"></Input> </Select>
</div> <span>]</span>
} <span className="eq-symbol">=</span>
{/* {item.type === 'file' ?
method !== 'POST' && <Input type="file" id={'file_' + index} onChange={e => this.changeBody(e, index, 'value')} multiple className="value" /> :
<div>GET 请求没有 BODY</div> <AutoComplete
*/} value={item.value}
onChange={e => this.changeBody(e, index, 'value')}
className="value"
dataSource={mockDataSource}
placeholder="参数值"
optionLabelProp="value"
/>
}
<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}>
<div className="res-code"></div>
<Collapse defaultActiveKey={['0', '1']} bordered={true}>
<Panel header="BODY" key="0" >
<div id="res-body-pretty" className="pretty-editor-body" style={{ display: isResJson ? '' : 'none' }}></div>
<TextArea
style={{ display: isResJson ? 'none' : '' }}
value={this.state.res && this.state.res.toString()}
autosize={{ minRows: 10, maxRows: 20 }}
></TextArea>
<h3 style={{ marginTop: '15px', display: isResJson ? '' : 'none' }}>返回 Body 验证结果</h3>
<div style={{ display: isResJson ? '' : 'none' }}>
{validResView}
</div>
</Panel>
<Panel header="HEADERS" key="1" >
{/*<TextArea
value={typeof this.state.resHeader === 'object' ? JSON.stringify(this.state.resHeader, null, 2) : this.state.resHeader.toString()}
autosize={{ minRows: 2, maxRows: 10 }}
></TextArea>*/}
<div id="res-headers-pretty" className="pretty-editor-header"></div>
</Panel> </Panel>
</Collapse> </Collapse>
</Card> </Spin>
<Card title="返回结果" noHovering className="resp-part">
<Spin spinning={this.state.loading}>
<div className="res-code"></div>
<Collapse defaultActiveKey={['0', '1']} bordered={true}>
<Panel header="BODY" key="0" >
<div id="res-body-pretty" className="pretty-editor-body" style={{ display: isResJson ? '' : 'none' }}></div>
<TextArea
style={{ display: isResJson ? 'none' : '' }}
value={this.state.res && this.state.res.toString()}
autosize={{ minRows: 10, maxRows: 20 }}
></TextArea>
<h3 style={{ marginTop: '15px', display: isResJson ? '' : 'none' }}>返回 Body 验证结果</h3>
<div style={{ display: isResJson ? '' : 'none' }}>
{validResView}
</div>
</Panel>
<Panel header="HEADERS" key="1" >
{/*<TextArea
value={typeof this.state.resHeader === 'object' ? JSON.stringify(this.state.resHeader, null, 2) : this.state.resHeader.toString()}
autosize={{ minRows: 2, maxRows: 10 }}
></TextArea>*/}
<div id="res-headers-pretty" className="pretty-editor-header"></div>
</Panel>
</Collapse>
</Spin>
</Card>
</div> </div>
) )
} }

View File

@ -14,3 +14,6 @@
min-height: 200px; min-height: 200px;
} }
} }
.interface-test {
padding: .24rem;
}

View File

@ -101,6 +101,9 @@ export default class GroupList extends Component {
}); });
} else { } else {
this.setState({ this.setState({
newGroupName: '',
group_name: '',
owner_uid: 0,
addGroupModalVisible: false addGroupModalVisible: false
}); });
} }
@ -111,6 +114,9 @@ export default class GroupList extends Component {
const res = await axios.post('/api/group/add', { group_name, group_desc, owner_uid }) const res = await axios.post('/api/group/add', { group_name, group_desc, owner_uid })
if (!res.data.errcode) { if (!res.data.errcode) {
this.setState({ this.setState({
newGroupName: '',
group_name: '',
owner_uid: 0,
addGroupModalVisible: false addGroupModalVisible: false
}); });
await this.props.fetchGroupList(); await this.props.fetchGroupList();

View File

@ -189,7 +189,7 @@ class InterfaceMenu extends Component {
that.props.history.push('/project/' + that.props.match.params.id + '/interface/api') that.props.history.push('/project/' + that.props.match.params.id + '/interface/api')
}, },
async onCancel() { onCancel() {
ref.destroy() ref.destroy()
} }
}); });

View File

@ -1,6 +1,6 @@
.interface-test { .interface-test {
.has-plugin, .req-part, .resp-part { .has-plugin, .req-part, .resp-part {
margin: 8px; margin-bottom: 16px;
} }
.url { .url {
display: flex; display: flex;
@ -25,7 +25,7 @@
margin-left: 6px; margin-left: 6px;
} }
.icon-btn:hover { .icon-btn:hover {
color: #108ee9; color: #2395f1;
} }
} }
.add-col-modal { .add-col-modal {
@ -41,7 +41,8 @@
background: #fa0; background: #fa0;
} }
.col-item.selected { .col-item.selected {
background: #108ee9; background: #2395f1;
color: rgba(255, 255, 255, 1);
} }
} }
} }

View File

@ -120,6 +120,6 @@
transform: translate(0, -50%); transform: translate(0, -50%);
} }
.user-name { .user-name {
padding-left: .32rem; padding-left: .38rem;
} }
} }

View File

@ -67,14 +67,18 @@ class groupController extends baseController {
return ctx.body = yapi.commons.resReturn(null, 400, '项目分组名不能为空'); return ctx.body = yapi.commons.resReturn(null, 400, '项目分组名不能为空');
} }
if (!params.owner_uid) { // if (!params.owner_uid) {
return ctx.body = yapi.commons.resReturn(null, 400, '项目分组必须添加一个组长'); // return ctx.body = yapi.commons.resReturn(null, 400, '项目分组必须添加一个组长');
} // }
let groupUserdata = await this.getUserdata(params.owner_uid, 'owner'); let groupUserdata = null;
if (groupUserdata === null) { if (params.owner_uid) {
return ctx.body = yapi.commons.resReturn(null, 400, '组长uid不存在') groupUserdata = await this.getUserdata(params.owner_uid, 'owner');
if (groupUserdata === null) {
return ctx.body = yapi.commons.resReturn(null, 400, '组长uid不存在')
}
} }
let groupInst = yapi.getInst(groupModel); let groupInst = yapi.getInst(groupModel);
let checkRepeat = await groupInst.checkRepeat(params.group_name); let checkRepeat = await groupInst.checkRepeat(params.group_name);
@ -89,7 +93,7 @@ class groupController extends baseController {
uid: this.getUid(), uid: this.getUid(),
add_time: yapi.commons.time(), add_time: yapi.commons.time(),
up_time: yapi.commons.time(), up_time: yapi.commons.time(),
members: [groupUserdata] members: groupUserdata ? [groupUserdata] : []
}; };
try { try {