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

This commit is contained in:
sean 2017-07-26 11:26:32 +08:00
commit a486469e02
25 changed files with 273 additions and 142 deletions

View File

@ -1,74 +0,0 @@
import {
FETCH_ADD_INTERFACE_INPUT,
FETCH_ADD_INTERFACE_TAG_VALUE,
FETCH_ADD_INTERFACE_HEADER_VALUE,
ADD_INTERFACE_SEQ_HEADER,
DELETE_INTERFACE_SEQ_HEADER,
GET_INTERFACE_REQ_PARAMS,
GET_INTERFACE_RES_PARAMS,
PUSH_INTERFACE_NAME,
PUSH_INTERFACE_METHOD
} from '../constants/action-types.js'
export function pushInputValue (value) {
return {
type: FETCH_ADD_INTERFACE_INPUT,
payload: value
};
}
export function reqTagValue (value) {
return {
type: FETCH_ADD_INTERFACE_TAG_VALUE,
payload: value
};
}
export function reqHeaderValue (value) {
return {
type: FETCH_ADD_INTERFACE_HEADER_VALUE,
payload: value
};
}
export function addReqHeader (value) {
return {
type: ADD_INTERFACE_SEQ_HEADER,
payload: value
};
}
export function deleteReqHeader (value) {
return {
type: DELETE_INTERFACE_SEQ_HEADER,
payload: value
};
}
export function getReqParams (value) {
return {
type: GET_INTERFACE_REQ_PARAMS,
payload: value
};
}
export function getResParams (value) {
return {
type: GET_INTERFACE_RES_PARAMS,
payload: value
};
}
export function pushInterfaceName (value) {
return {
type: PUSH_INTERFACE_NAME,
payload: value
}
}
export function pushInterfaceMethod (value) {
return {
type: PUSH_INTERFACE_METHOD,
payload: value
}
}

View File

@ -7,8 +7,10 @@ import {
GET_INTERFACE_REQ_PARAMS,
GET_INTERFACE_RES_PARAMS,
PUSH_INTERFACE_NAME,
PUSH_INTERFACE_METHOD
PUSH_INTERFACE_METHOD,
FETCH_INTERFACE_PROJECT
} from '../constants/action-types.js'
import axios from 'axios'
export function pushInputValue (value) {
return {
@ -72,3 +74,10 @@ export function pushInterfaceMethod (value) {
payload: value
}
}
export function fetchInterfaceProject(id) {
return {
type: FETCH_INTERFACE_PROJECT,
payload: axios.get('/project/get', { params: {id}})
}
}

View File

@ -11,7 +11,6 @@ import axios from 'axios';
const checkLoginState = () => {
return(dispatch)=> {
axios.get('/user/status').then((res) => {
console.log(res);
dispatch({
type: GET_LOGIN_STATE,
payload: res

View File

@ -31,7 +31,7 @@ export default class Srch extends Component{
}
onSelect = (value) => {
if( value.split(":")[0] == "group" ){
if( value.split(":")[0] == "分组" ){
this.props.history.push('/group/'+value.split(":")[1].trim());
} else {
this.props.history.push('/project/'+value.split("(")[1].slice(0,-1));
@ -45,7 +45,7 @@ export default class Srch extends Component{
const dataSource = [];
for(let title in res.data.data){
res.data.data[title].map(item => {
title == "group" ? dataSource.push( title+": "+item.groupName ): dataSource.push( title+": "+item.name+"("+item._id+")" );
title == "group" ? dataSource.push( "分组"+": "+item.groupName ): dataSource.push( "项目"+": "+item.name+"("+item._id+")" );
})
}
this.setState({
@ -84,8 +84,13 @@ export default class Srch extends Component{
>
<Input
prefix={<Icon type="search" className="srch-icon" />}
size="large" style={{ width: 200 }}
placeholder="search group/project"
size="large"
style={{
// width: 200,
// borderColor:"#AAA",
// borderWidth:"1px",
}}
placeholder="搜索分组/项目"
className="search-input"
/>
</AutoComplete>

View File

@ -2,10 +2,14 @@ $color-grey:#979DA7;
.search-wrapper{
.search-input{
border:1px solid #AAA;
background-color: rgba(255,255,255,0.5);
border:1px solid $color-grey;
color: $color-grey;
width: 2rem;
transition: width 2s;
//background-color:rgba(0,0,0,0.5);
}
.srch-icon{
color: $color-grey;
font-weight: bold;
}
}

View File

@ -14,6 +14,7 @@ export const DELETE_INTERFACE_SEQ_HEADER = 'DELETE_INTERFACE_SEQ_HEADER'
export const GET_INTERFACE_REQ_PARAMS = 'GET_INTERFACE_REQ_PARAMS'
export const GET_INTERFACE_RES_PARAMS = 'GET_INTERFACE_RES_PARAMS'
export const PUSH_INTERFACE_METHOD = 'PUSH_INTERFACE_METHOD'
export const FETCH_INTERFACE_PROJECT = 'FETCH_INTERFACE_PROJECT'
// group
export const FETCH_GROUP_LIST = 'FETCH_GROUP_LIST'

View File

@ -10,13 +10,16 @@ import ReqHeader from './ReqHeader/ReqHeader.js'
import ReqParams from './ReqParams/ReqParams.js'
import ResParams from './ResParams/ResParams.js'
import Result from './Result/Result.js'
import {
import InterfaceTest from './InterfaceTest/InterfaceTest.js'
import {
saveForms,
getResParams,
getReqParams,
addReqHeader,
pushInputValue,
pushInterfaceName
pushInterfaceName,
fetchInterfaceProject,
pushInterfaceMethod
} from '../../actions/addInterface.js'
const success = () => {
@ -40,7 +43,9 @@ const success = () => {
getResParams,
addReqHeader,
pushInputValue,
pushInterfaceName
pushInterfaceName,
fetchInterfaceProject,
pushInterfaceMethod
}
)
@ -105,12 +110,14 @@ class AddInterface extends Component {
editState (data) {
const props = this.props
const { path, title, req_params_other, res_body, req_headers} = data
const { path, title, req_params_other, res_body, req_headers, project_id, method } = data
props.pushInputValue(path)
props.pushInterfaceMethod(method)
props.pushInterfaceName(title)
props.getReqParams(req_params_other)
props.getResParams(res_body)
props.addReqHeader(req_headers)
props.fetchInterfaceProject(project_id)
}
initInterfaceData (interfaceId) {
@ -204,7 +211,9 @@ class AddInterface extends Component {
<Result isSave={isSave} />
</TabPane>
<TabPane tab="Mock" key="2">mock</TabPane>
<TabPane tab="测试" key="3">测试</TabPane>
<TabPane tab="测试" key="3">
<InterfaceTest />
</TabPane>
</Tabs>
</div>
<div className={`loading ${isLoading}`}></div>
@ -214,5 +223,3 @@ class AddInterface extends Component {
}
export default AddInterface

View File

@ -0,0 +1,120 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Input } from 'antd'
import { autobind } from 'core-decorators';
import crossRequest from 'cross-request';
import { withRouter } from 'react-router';
import URL from 'url';
import {
} from '../../../actions/group.js'
import './InterfaceTest.scss'
@connect(
state => ({
reqParams: state.addInterface.reqParams,
method: state.addInterface.method,
url: state.addInterface.url,
seqGroup: state.addInterface.seqGroup,
interfaceName: state.addInterface.interfaceName,
interfaceProject: state.addInterface.project
}),
{
}
)
@withRouter
export default class InterfaceTest extends Component {
static propTypes = {
reqParams: PropTypes.string,
method: PropTypes.string,
url: PropTypes.string,
interfaceName: PropTypes.string,
seqGroup: PropTypes.array,
match: PropTypes.object,
interfaceProject: PropTypes.object
}
state = {
res: {},
header: {}
}
constructor(props) {
super(props)
}
componentWillMount() {
}
@autobind
testInterface() {
crossRequest({
url: 'http://petstore.swagger.io/v2/swagger.json',
method: 'GET',
data: {
a:1
},
success: (res, header) => {
this.setState({res})
console.log(header)
}
})
}
render () {
const { method, url, seqGroup, interfaceName, interfaceProject } = this.props;
const { prd_host, basepath, protocol } = interfaceProject;
const reqParams = JSON.parse(this.props.reqParams);
let query = {};
if (method === 'GET') {
Object.keys(reqParams).forEach(key => {
const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams) : reqParams.toString();
query[key] = value;
})
}
const href = URL.format({
protocol: protocol || 'http',
host: prd_host,
pathname: URL.resolve(basepath, url),
query
});
return (
<div>
<div>接口名{interfaceName}</div>
<div>
METHOD: <Input value={method} disabled />
URL: <Input value={href} />
HEADERS: <Input value={JSON.stringify(seqGroup, 2)} />
请求参数
<div>
{
Object.keys(reqParams).map((key, index) => {
const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams) : reqParams.toString();
return (
<div key={index}>
<Input value={key} style={{display: 'inline-block', width: 200}} />{' = '}
<Input value={value} style={{display: 'inline-block', width: 200}} />
</div>
)
})
}
</div>
</div>
<Button onClick={this.testInterface}>发送跨域请求</Button>
<div>
返回结果
{JSON.stringify(this.state.res, 2)}
</div>
</div>
)
}
}

View File

@ -40,7 +40,7 @@ const HomeGuest = (props) => (
<div className="feat-part">
<div className="container">
<OverPack
playScale="0.3"
playScale={[0.3,0.1]}
>
<TweenOne
key="feat-motion-one"
@ -52,13 +52,13 @@ const HomeGuest = (props) => (
<Row key="feat-motion-row">
<QueueAnim
delay = {200}
interval ={100}
type = "bottom"
interval ={200}
leaveReverse={true}
ease = 'easeOutQuad'
animConfig ={{ opacity:[1,0],y: '+=30' }}
key="feat-motion-queue"
>
<Col span={8} className="feat-wrapper" key="feat-wrapper-1">
<Col span={6} className="feat-wrapper" key="feat-wrapper-1">
<div className="feat-img">
<Icon type="api" />
</div>
@ -66,7 +66,7 @@ const HomeGuest = (props) => (
接口管理
</p>
</Col>
<Col span={8} className="feat-wrapper" key="feat-wrapper-2">
<Col span={6} className="feat-wrapper" key="feat-wrapper-2">
<div className="feat-img">
<Icon type="link" />
</div>
@ -74,7 +74,7 @@ const HomeGuest = (props) => (
支持Mock
</p>
</Col>
<Col span={8} className="feat-wrapper" key="feat-wrapper-3">
<Col span={6} className="feat-wrapper" key="feat-wrapper-3">
<div className="feat-img">
<Icon type="team" />
</div>
@ -82,6 +82,14 @@ const HomeGuest = (props) => (
团队协作
</p>
</Col>
<Col span={6} className="feat-wrapper" key="feat-wrapper-4">
<div className="feat-img">
<Icon type="desktop" />
</div>
<p className="feat-title">
可部署
</p>
</Col>
</QueueAnim>
</Row>
</OverPack>
@ -132,7 +140,7 @@ class Home extends Component {
<div className="user-home">
<div className="user-des">
<p className="title">YAPI</p>
<p className="des">一个高效易用功能强大的api管理系统</p>
<p className="des">一个高效易用可部署的Api管理系统</p>
<div className="btn">
<Button type="primary" size="large">
<Link to="/group" onClick={this.toStart}>开始</Link>

View File

@ -111,43 +111,48 @@ $color-black-lighter: #404040;
height:100%;
position: relative;
}
.feat-wrapper{
.feat-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
.feat-img{
.feat-img {
height: 1.2rem;
width: 1.2rem;
border-radius: 100%;
margin-bottom: .2rem;
color: $color-white;
i{
i {
line-height: 1.2rem;
font-size: .6rem;
}
}
.feat-title{
.feat-title {
font-size: .16rem;
line-height: .3rem;
color: #333;
}
&:first-child{
.feat-img{
&:first-child {
.feat-img {
background-color: rgb(248, 88, 96);
}
}
&:nth-child(2){
.feat-img{
&:nth-child(2) {
.feat-img {
background-color: #f9bb13;
}
}
&:nth-child(3){
.feat-img{
&:nth-child(3) {
.feat-img {
background-color: #20ab8e;
}
}
&:nth-child(4) {
.feat-img {
background-color: rgb(66, 165, 245);
}
}
}
}

View File

@ -75,6 +75,9 @@ class InterfaceTable extends Component {
<Button type="primary">
<Link to={`/AddInterface/edit/${data._id}`}>编辑</Link>
</Button>
<Button type="primary">
<Link to={`/AddInterface/edit/${data._id}`}>测试</Link>
</Button>
<Button type="danger" onClick={deleteInterface}>删除</Button>
</span>
)

View File

@ -68,11 +68,9 @@ export default class GroupList extends Component {
this.props.history.replace(`${currGroup.group_name}`);
}
}
console.log(groupName);
}else if(!groupName && this.props.groupList.length){
this.props.history.push(`/group/${this.props.groupList[0].group_name}`);
}
console.log(currGroup);
this.setState({groupList: this.props.groupList});
this.props.setCurrGroup(currGroup)
});
@ -205,11 +203,11 @@ export default class GroupList extends Component {
<div className="group-bar">
<div className="curr-group">
<div className="curr-group-name">
{currGroup.group_name}
<div className="text" title={currGroup.group_name}>{currGroup.group_name}</div>
<Icon className="edit-group" type="edit" title="编辑分组" onClick={() => this.showModal(TYPE_EDIT)}/>
<Icon className="delete-group" type="delete" title="删除分组" onClick={this.deleteGroup}/>
</div>
<div className="curr-group-desc">简介{currGroup.group_desc}</div>
<div className="curr-group-desc" title={currGroup.group_desc}>简介{currGroup.group_desc}</div>
</div>
<div className="group-operate">
<div className="search">
@ -268,7 +266,7 @@ export default class GroupList extends Component {
<Row gutter={6} className="modal-input">
<Col span="5"><div className="label">简介</div></Col>
<Col span="15">
<Input placeholder="请输入分组描述" value={this.state.currGroupDesc} onChange={(e) => this.inputNewGroupDesc(e, TYPE_EDIT)}></Input>
<TextArea rows={3} placeholder="请输入分组描述" value={this.state.currGroupDesc} onChange={(e) => this.inputNewGroupDesc(e, TYPE_EDIT)}></TextArea>
</Col>
</Row>
</Modal>

View File

@ -5,12 +5,25 @@
border-radius: 4px 4px 0 0;
padding: 32px 24px;
.curr-group-name {
.text {
display: inline-block;
overflow:hidden;
white-space:nowrap;
text-overflow:ellipsis;
max-width: 150px;
}
color: #fff;
font-size: 24px;
}
.curr-group-desc {
color: #fff;
font-size: 12px;
max-height: 54px;
text-overflow:ellipsis;
overflow:hidden;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
display: -webkit-box;
}
.delete-group, .edit-group {
font-size: 18px;

View File

@ -41,7 +41,7 @@ const getColumns = (data, props) => {
dataIndex: 'name',
key: 'name',
render: (text, record) => {
return <Link to={`Interface/${record._id}`}>{text}</Link>
return <Link to={`/Interface/${record._id}`}>{text}</Link>
}
}, {
title: '创建人',

View File

@ -141,8 +141,9 @@ class UpDateModal extends Component {
// can use data-binding to set
form.setFieldsValue({
envs: envs.filter(key => {
const realKey = key._id ? key._id : key
console.log(key);
return key._id !== id;
return realKey !== id;
})
});
}
@ -183,7 +184,7 @@ class UpDateModal extends Component {
getFieldDecorator('envs', { initialValue: envMessage });
const envs = getFieldValue('envs');
const formItems = envs.map((k, index) => {
// console.log(k);
console.log(k);
const secondIndex = 'next' + index; // 为保证key的唯一性
return (
<Row key={index} type="flex" justify="space-between" align={index === 0 ? 'middle' : 'top'}>
@ -229,7 +230,7 @@ class UpDateModal extends Component {
>
{getFieldDecorator(`envs-domain-${index}`, {
validateTrigger: ['onChange', 'onBlur'],
initialValue: envMessage.length !== 0 ? k.domain.split('\/\/')[1] : '',
initialValue: envMessage.length !== 0 && k.domain ? k.domain.split('\/\/')[1] : '',
rules: [{
required: false,
whitespace: true,
@ -269,7 +270,7 @@ class UpDateModal extends Component {
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
onClick={() => this.remove(k._id)}
onClick={() => this.remove(k._id ? k._id : k)}
/>
) : null}
</Col>

View File

@ -10,7 +10,7 @@ const Option = AutoComplete.Option;
state => {
console.log(state);
return {
curUid: state.user.curUid
curUid: state.login.uid + ''
}
}
)

View File

@ -4,7 +4,6 @@ import { Link } from 'react-router-dom'
//import PropTypes from 'prop-types'
import {
Table,
Button,
Popconfirm,
message
} from 'antd'
@ -79,11 +78,13 @@ class List extends Component {
let columns = [{
title: 'UID',
dataIndex: '_id',
key: '_id'
key: '_id',
width: 70
}, {
title: '用户名',
dataIndex: 'username',
key: 'username'
key: 'username',
width: 150
}, {
title: 'Email',
dataIndex: 'email',
@ -91,20 +92,24 @@ class List extends Component {
}, {
title: '用户角色',
dataIndex: 'role',
key: 'role'
key: 'role',
width:110
}, {
title: '更新日期',
dataIndex: 'up_time',
key: 'up_time'
key: 'up_time',
width: 180
}, {
title: '功能',
key: 'action',
width:80,
render: (item) => {
return (
<span>
<Button type="primary"><Link to={"/user/profile/" + item._id} > 查看 </Link></Button>
<Link to={"/user/profile/" + item._id} >查看</Link>
<span className="ant-divider" />
<Popconfirm placement="leftTop" title="确认删除此用户?" onConfirm={() => {this.confirm(item._id)}} okText="Yes" cancelText="No">
<Button type="danger">删除</Button>
<a href="#">删除</a>
</Popconfirm>
</span>
)
@ -128,7 +133,7 @@ class List extends Component {
return (
<section className="user-table">
<Table columns={columns} pagination={pageConfig} dataSource={data} />
<Table bordered={true} columns={columns} pagination={pageConfig} dataSource={data} />
</section>
)

View File

@ -4,10 +4,11 @@
display: -webkit-box;
-webkit-box-flex: 1;
margin: .88rem auto 0 auto;
font-size: 0.14rem;
// font-size: 0.14rem;
min-height:500px;
margin-top: 84px;
min-height:433px;
margin-top: 40px;
margin-bottom: 55px;
@ -16,6 +17,7 @@
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
background: #FFF;
border-radius:5px;
margin-top: 15px;
.search{
margin: 5px;
}
@ -29,20 +31,22 @@
border-radius:5px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
background: #FFF;
margin-top: 15px;
.ant-table-wrapper table {
font-size: .14rem;
// font-size: .14rem;
button {
margin: 0 10px 0 0;
a {
// font-size: 12px;
}
}
}
.user-profile {
-webkit-box-flex: 1;
margin-top: 15px;
margin-left: 15px;
padding: 10px;
padding: 10px 30px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
background: #FFF;
border-radius:5px;
@ -51,9 +55,21 @@
line-height:35px;
margin: 5px;
margin-bottom:10px;
border-bottom: 1px solid #f1f3f6;
padding-bottom: 10px;
.ant-col-4{
color: black;
padding: 0px 10px;
text-indent: .7em;
// background-color: #f1f3f6;
border-left: 5px solid #f1f3f6;
margin-right: 30px;
}
.text{
padding-right: 15px;
}
.text-button{
font-size: 12px;
// font-size: 12px;
color: #657289;
cursor: pointer
}

View File

@ -25,7 +25,8 @@ export default (state = initialState, action) => {
...state,
isLogin: (action.payload.data.errcode == 0),
loginState: (action.payload.data.errcode == 0)?MEMBER_STATUS:GUEST_STATUS,
userName: action.payload.data.data ? action.payload.data.data.username : null
userName: action.payload.data.data ? action.payload.data.data.username : null,
uid: action.payload.data.data ? action.payload.data.data._id : null
};
}
case LOGIN: {

View File

@ -7,7 +7,8 @@ import {
GET_INTERFACE_REQ_PARAMS,
GET_INTERFACE_RES_PARAMS,
PUSH_INTERFACE_NAME,
PUSH_INTERFACE_METHOD
PUSH_INTERFACE_METHOD,
FETCH_INTERFACE_PROJECT
} from '../../constants/action-types.js'
const initialState = {
@ -23,7 +24,8 @@ const initialState = {
}
],
reqParams: '',
resParams: ''
resParams: '',
project: {}
}
export default (state = initialState, action) => {
@ -73,6 +75,11 @@ export default (state = initialState, action) => {
...state,
method: action.payload
}
case FETCH_INTERFACE_PROJECT:
return {
...state,
project: action.payload.data.data
}
default:
return state
}

View File

@ -25,7 +25,8 @@ export default (state = initialState, action) => {
...state,
isLogin: (action.payload.data.errcode == 0),
loginState: (action.payload.data.errcode == 0)?MEMBER_STATUS:GUEST_STATUS,
userName: action.payload.data.data ? action.payload.data.data.username : null
userName: action.payload.data.data ? action.payload.data.data.username : null,
uid: action.payload.data.data ? action.payload.data.data._id : null
};
}
case LOGIN: {

View File

@ -20,6 +20,7 @@
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"copy-webpack-plugin": "^4.0.1",
"core-decorators": "^0.17.0",
"cross-request": "^1.0.1",
"fs-extra": "^3.0.1",
"jsoneditor": "^5.9.3",
"jsonwebtoken": "^7.4.1",
@ -51,6 +52,7 @@
"sha1": "^1.1.1",
"string-replace-webpack-plugin": "^0.1.3",
"universal-cookie": "^2.0.8",
"url": "^0.11.0",
"wangeditor": "^3.0.4",
"ykit-config-antd": "^0.1.3",
"ykit-config-react": "^0.4.4"

View File

@ -26,7 +26,7 @@ class projectController extends baseController {
verifyDomain(domain){
if(!domain) return false;
if(/^[a-zA-Z0-9\-_\.]+[a-zA-Z]{2,6}$/.test(domain)){
if(/^[a-zA-Z0-9\-_\.]+?\.[a-zA-Z0-9\-_\.]*?[a-zA-Z]{2,6}$/.test(domain)){
return true;
}
return false;
@ -332,7 +332,7 @@ class projectController extends baseController {
let projectData = await this.Model.get(id);
if(params.basepath = (this.handleBasepath(params.basepath)) === false){
if((params.basepath = this.handleBasepath(params.basepath)) === false){
return ctx.body = yapi.commons.resReturn(null, 401, 'basepath格式有误')
}

View File

@ -86,7 +86,7 @@ var projectController = function (_baseController) {
key: 'verifyDomain',
value: function verifyDomain(domain) {
if (!domain) return false;
if (/^[a-zA-Z0-9\-_\.]+[a-zA-Z]{2,6}$/.test(domain)) {
if (/^[a-zA-Z0-9\-_\.]+?\.[a-zA-Z0-9\-_\.]*?[a-zA-Z]{2,6}$/.test(domain)) {
return true;
}
return false;
@ -778,7 +778,7 @@ var projectController = function (_baseController) {
case 12:
projectData = _context8.sent;
if (!(params.basepath = this.handleBasepath(params.basepath) === false)) {
if (!((params.basepath = this.handleBasepath(params.basepath)) === false)) {
_context8.next = 15;
break;
}