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

This commit is contained in:
gaoxiaolin.gao 2017-12-11 20:45:44 +08:00
commit 1b9c4ccf73
24 changed files with 1526 additions and 1279 deletions

View File

@ -4,8 +4,11 @@
1. Api 路径兼容 postman {varible}
2. View Response Height 问题
#### Feature
1. 新增克隆测试集功能
2. 高级 Mock 期望支持 mockjs
3. pathname 允许只有一个 /
4. 高级 Mock 期望支持 mockjs
### v1.2.8

View File

@ -45,7 +45,9 @@ YApi 是<strong>高效</strong>、<strong>易用</strong>、<strong>功能强大
密码: ymfe.org
```
### YApi 资源
* [yapi docker](https://github.com/branchzero/yapi-docker) By branchzero
* [yapi sso 登录插件](https://github.com/YMFE/yapi-plugin-qsso)
* [yapi docker 部署](https://github.com/branchzero/yapi-docker) By branchzero
### YApi 的一些客户

View File

@ -5,7 +5,7 @@ import PropTypes from 'prop-types'
/**
* @author suxiaoxin
* @demo
* <EasyDragSort data={this.state.list} onChange={this.handleChange} >
* <EasyDragSort data={()=>this.state.list} onChange={this.handleChange} >
* {list}
* </EasyDragSot>
*/
@ -13,16 +13,19 @@ let curDragIndex = null;
EasyDragSort.propTypes = {
children: PropTypes.array,
data: PropTypes.array,
onChange: PropTypes.func,
onDragEnd: PropTypes.func
onDragEnd: PropTypes.func,
data: PropTypes.func
}
export default function EasyDragSort(props){
let container = props.children;
function onChange(from, to){
const onChange = (from, to)=>{
if(from === to ) return ;
let curValue = props.data;
let curValue;
curValue = props.data();
let newValue = arrMove(curValue, from, to);
if(typeof props.onChange === 'function'){
return props.onChange(newValue, from ,to);

View File

@ -1,430 +1,475 @@
import React, { PureComponent as Component } from 'react'
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import PropTypes from 'prop-types'
import { fetchInterfaceColList, fetchInterfaceCaseList, setColData, fetchCaseList } from '../../../../reducer/modules/interfaceCol'
import { fetchInterfaceList } from '../../../../reducer/modules/interface.js';
import axios from 'axios';
// import { Input, Icon, Button, Modal, message, Tooltip, Tree, Dropdown, Menu, Form } from 'antd';
import ImportInterface from './ImportInterface'
import { Input, Icon, Button, Modal, message, Tooltip, Tree, Form } from 'antd';
const TreeNode = Tree.TreeNode;
const FormItem = Form.Item;
const confirm = Modal.confirm;
import './InterfaceColMenu.scss'
const ColModalForm = Form.create()((props) => {
const { visible, onCancel, onCreate, form, title } = props;
const { getFieldDecorator } = form;
return (
<Modal
visible={visible}
title={title}
onCancel={onCancel}
onOk={onCreate}
>
<Form layout="vertical">
<FormItem label="集合名">
{getFieldDecorator('colName', {
rules: [{ required: true, message: '请输入集合命名!' }]
})(
<Input />
)}
</FormItem>
<FormItem label="简介">
{getFieldDecorator('colDesc')(<Input type="textarea" />)}
</FormItem>
</Form>
</Modal>
)
});
@connect(
state => {
return {
interfaceColList: state.interfaceCol.interfaceColList,
currColId: state.interfaceCol.currColId,
currCaseId: state.interfaceCol.currCaseId,
isShowCol: state.interfaceCol.isShowCol,
isRander: state.interfaceCol.isRander,
list: state.inter.list
}
},
{
fetchInterfaceColList,
fetchInterfaceCaseList,
fetchInterfaceList,
fetchCaseList,
setColData
}
)
@withRouter
export default class InterfaceColMenu extends Component {
static propTypes = {
match: PropTypes.object,
interfaceColList: PropTypes.array,
fetchInterfaceColList: PropTypes.func,
fetchInterfaceCaseList: PropTypes.func,
fetchInterfaceList: PropTypes.func,
fetchCaseList: PropTypes.func,
setColData: PropTypes.func,
history: PropTypes.object,
currColId: PropTypes.number,
currCaseId: PropTypes.number,
isShowCol: PropTypes.bool,
isRander: PropTypes.bool,
list: PropTypes.array
}
state = {
expandedKeys: [],
colModalType: '',
colModalVisible: false,
editColId: 0,
filterValue: '',
importInterVisible: false,
importInterIds: [],
importColId: 0
}
constructor(props) {
super(props)
}
async componentWillMount() {
const { isShowCol, currColId, currCaseId } = this.props;
const action = isShowCol ? 'col' : 'case';
const actionId = isShowCol ? currColId : currCaseId;
this.setState({ expandedKeys: [action + '_' + actionId] })
}
async componentWillReceiveProps(nextProps) {
const { currColId } = nextProps;
let expandedKeys = this.state.expandedKeys;
if (expandedKeys.indexOf('col_' + currColId) === -1) {
expandedKeys = expandedKeys.concat(['col_' + currColId])
}
this.setState({ expandedKeys })
}
addorEditCol = async () => {
const { colName: name, colDesc: desc } = this.form.getFieldsValue();
const { colModalType, editColId: col_id } = this.state;
const project_id = this.props.match.params.id;
let res = {};
if (colModalType === 'add') {
res = await axios.post('/api/col/add_col', { name, desc, project_id })
} else if (colModalType === 'edit') {
res = await axios.post('/api/col/up_col', { name, desc, col_id })
}
if (!res.data.errcode) {
this.setState({
colModalVisible: false
});
message.success(colModalType === 'edit' ? '修改集合成功' : '添加集合成功');
await this.props.fetchInterfaceColList(project_id);
} else {
message.error(res.data.errmsg);
}
}
onExpand = (keys) => {
this.setState({ expandedKeys: keys })
}
onSelect = (keys) => {
if (keys.length) {
const type = keys[0].split('_')[0];
const id = keys[0].split('_')[1];
const project_id = this.props.match.params.id
if (type === 'col') {
this.props.setColData({
isShowCol: true,
isRander: false,
currColId: +id
})
this.props.history.push('/project/' + project_id + '/interface/col/' + id)
} else {
this.props.setColData({
isShowCol: false,
isRander: false,
currCaseId: +id
})
this.props.history.push('/project/' + project_id + '/interface/case/' + id)
}
}
}
showDelColConfirm = (colId) => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试集合',
content: '温馨提示:该操作会删除该集合下所有测试用例,用例删除后无法恢复',
async onOk() {
const res = await axios.get('/api/col/del_col?col_id=' + colId)
if (!res.data.errcode) {
message.success('删除集合成功');
const result = await that.props.fetchInterfaceColList(that.props.match.params.id);
const nextColId = result.payload.data.data[0]._id;
that.props.history.push('/project/' + params.id + '/interface/col/' + nextColId);
} else {
message.error(res.data.errmsg);
}
}
});
}
showNoDelColConfirm = () => {
confirm({
title: '此测试集合为最后一个集合',
content: '温馨提示:建议不要删除'
});
}
showDelCaseConfirm = (caseId) => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试用例',
content: '温馨提示:用例删除后无法恢复',
async onOk() {
const res = await axios.get('/api/col/del_case?caseid=' + caseId)
if (!res.data.errcode) {
message.success('删除用例成功');
// 如果删除当前选中 case切换路由到集合
if (+caseId === +that.props.currCaseId) {
that.props.history.push('/project/' + params.id + '/interface/col/')
} else {
that.props.fetchInterfaceColList(that.props.match.params.id);
that.props.setColData({ currColId: +that.props.currColId, isRander: true })
}
} else {
message.error(res.data.errmsg);
}
}
});
}
showColModal = (type, col) => {
const editCol = type === 'edit' ? { colName: col.name, colDesc: col.desc } : { colName: '', colDesc: '' };
this.setState({
colModalVisible: true,
colModalType: type || 'add',
editColId: col && col._id
})
this.form.setFieldsValue(editCol)
}
saveFormRef = (form) => {
this.form = form;
}
selectInterface = (importInterIds) => {
// console.log(importInterIds)
this.setState({ importInterIds })
}
showImportInterfaceModal = async (colId) => {
const projectId = this.props.match.params.id;
await this.props.fetchInterfaceList(projectId)
this.setState({ importInterVisible: true, importColId: colId })
}
handleImportOk = async () => {
const project_id = this.props.match.params.id;
const { importColId, importInterIds } = this.state;
const res = await axios.post('/api/col/add_case_list', {
interface_list: importInterIds,
col_id: importColId,
project_id
})
if (!res.data.errcode) {
this.setState({ importInterVisible: false })
message.success('导入集合成功');
await this.props.fetchInterfaceColList(project_id);
this.props.setColData({ currColId: +this.props.currColId, isRander: true })
} else {
message.error(res.data.errmsg);
}
}
handleImportCancel = () => {
this.setState({ importInterVisible: false })
}
filterCol = (e) => {
const value = e.target.value;
this.setState({ filterValue: value })
}
onDrop = async (e) => {
console.log('e', e);
const projectId = this.props.match.params.id;
const dropColIndex = e.node.props.pos.split('-')[1];
const dropColId = this.props.interfaceColList[dropColIndex]._id;
const id = e.dragNode.props.eventKey;
const dragColIndex = e.dragNode.props.pos.split('-')[1];
const dragColId = this.props.interfaceColList[dragColIndex]._id;
if (id.indexOf('col') === -1 && dropColId !== dragColId) {
// if (dropColId !== dragColId) {
//
// }
// else {
// let caseList = this.props.interfaceColList[dropColIndex].caseList;
// const dropIndex = e.node.props.pos.split('-')[2];
// const dragIndex = e.dragNode.props.pos.split('-')[2];
// // caseList[dropIndex] = [caseList[dragIndex], caseList[dragIndex] = caseList[dropIndex]][0]
// let newArr = [].concat(caseList);
// newArr[dragIndex] = caseList[dropIndex];
// newArr[dropIndex] = caseList[dragIndex];
// let changes = [];
// newArr.forEach((item, index) => {
// changes.push({
// id: item._id,
// index: index
// })
// })
// axios.post('/api/col/up_col_index', changes).then()
// }
await axios.post('/api/col/up_case', { id: id.split('_')[1], col_id: dropColId });
this.props.fetchInterfaceColList(projectId);
this.props.setColData({ currColId: +this.props.currColId, isShowCol: true, isRander: true })
}
}
render() {
const { currColId, currCaseId, isShowCol } = this.props;
const { colModalType, colModalVisible, filterValue, importInterVisible } = this.state;
// const menu = (col) => {
// return (
// <Menu>
// <Menu.Item>
// <span onClick={() => this.showColModal('edit', col)}>修改集合</span>
// </Menu.Item>
// <Menu.Item>
// <span onClick={() => {
// this.showDelColConfirm(col._id)
// }}>删除集合</span>
// </Menu.Item>
// <Menu.Item>
// <span onClick={() => this.showImportInterface(col._id)}>导入接口</span>
// </Menu.Item>
// </Menu>
// )
// };
let isFilterCat = false;
// console.log();
// let caseList = this.props.interfaceColList.caseList;
// if(caseList&&caseList.length>1){
// caseList = caseList.sort((a,b)=>{
// return a.index-b.index;
// });
// this.props.interfaceColList.caseList = caseList;
// }
const list = this.props.interfaceColList.filter(col => {
if (col.name.indexOf(filterValue) !== -1) {
isFilterCat = true;
return true;
}
isFilterCat = false;
let caseList = col.caseList.filter(item => {
return item.casename.indexOf(filterValue) !== -1
})
return caseList.length > 0;
});
return (
<div>
<div className="interface-filter">
<Input placeholder="搜索测试集合" onChange={this.filterCol} />
<Tooltip placement="bottom" title="添加集合">
<Button type="primary" style={{ marginLeft: "16px" }} onClick={() => this.showColModal('add')} className="btn-filter" >添加集合</Button>
</Tooltip>
</div>
<Tree
className="col-list-tree"
expandedKeys={this.state.expandedKeys}
selectedKeys={[isShowCol ? 'col_' + currColId : 'case_' + currCaseId]}
onSelect={this.onSelect}
autoExpandParent
onExpand={this.onExpand}
ondragstart={() => { return false }}
>
{
list.map((col) => (
<TreeNode
key={'col_' + col._id}
title={
<div className="menu-title">
<span><Icon type="folder-open" style={{ marginRight: 5 }} /><span>{col.name}</span></span>
<div className="btns">
<Tooltip title="删除集合">
<Icon type='delete' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); list.length > 1 ? this.showDelColConfirm(col._id) : this.showNoDelColConfirm() }} />
</Tooltip>
<Tooltip title="编辑集合">
<Icon type='edit' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.showColModal('edit', col) }} />
</Tooltip>
<Tooltip title="导入接口">
<Icon type='plus' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.showImportInterfaceModal(col._id) }} />
</Tooltip>
</div>
{/*<Dropdown overlay={menu(col)} trigger={['click']} onClick={e => e.stopPropagation()}>
<Icon className="opts-icon" type='ellipsis'/>
</Dropdown>*/}
</div>
}
>
{
col.caseList && col.caseList.filter((item) => {
if (isFilterCat) {
return true;
}
return item.casename.indexOf(filterValue) !== -1
}).sort((a, b) => {
return a.index - b.index;
}).map((interfaceCase) => (
<TreeNode
style={{ width: '100%' }}
key={'case_' + interfaceCase._id}
title={
<div className="menu-title" title={interfaceCase.casename}>
<span className="casename">{interfaceCase.casename}</span>
<Tooltip title="删除用例">
<Icon type='delete' className="case-delete-icon" onClick={(e) => { e.stopPropagation(); this.showDelCaseConfirm(interfaceCase._id) }} />
</Tooltip>
</div>
}
></TreeNode>
))
}
</TreeNode>
))
}
</Tree>
<ColModalForm
ref={this.saveFormRef}
type={colModalType}
visible={colModalVisible}
onCancel={() => { this.setState({ colModalVisible: false }) }}
onCreate={this.addorEditCol}
></ColModalForm>
<Modal
title="导入接口到集合"
visible={importInterVisible}
onOk={this.handleImportOk}
onCancel={this.handleImportCancel}
width={800}
>
<ImportInterface onChange={this.selectInterface} list={this.props.list} />
</Modal>
</div>
)
}
}
import React, { PureComponent as Component } from 'react'
import { connect } from 'react-redux';
import { withRouter } from 'react-router'
import PropTypes from 'prop-types'
import { fetchInterfaceColList, fetchInterfaceCaseList, setColData, fetchCaseList } from '../../../../reducer/modules/interfaceCol'
import { fetchInterfaceList } from '../../../../reducer/modules/interface.js';
import axios from 'axios';
// import { Input, Icon, Button, Modal, message, Tooltip, Tree, Dropdown, Menu, Form } from 'antd';
import ImportInterface from './ImportInterface'
import { Input, Icon, Button, Modal, message, Tooltip, Tree, Form } from 'antd';
const TreeNode = Tree.TreeNode;
const FormItem = Form.Item;
const confirm = Modal.confirm;
import './InterfaceColMenu.scss'
const ColModalForm = Form.create()((props) => {
const { visible, onCancel, onCreate, form, title } = props;
const { getFieldDecorator } = form;
return (
<Modal
visible={visible}
title={title}
onCancel={onCancel}
onOk={onCreate}
>
<Form layout="vertical">
<FormItem label="集合名">
{getFieldDecorator('colName', {
rules: [{ required: true, message: '请输入集合命名!' }]
})(
<Input />
)}
</FormItem>
<FormItem label="简介">
{getFieldDecorator('colDesc')(<Input type="textarea" />)}
</FormItem>
</Form>
</Modal>
)
});
@connect(
state => {
return {
interfaceColList: state.interfaceCol.interfaceColList,
currColId: state.interfaceCol.currColId,
currCaseId: state.interfaceCol.currCaseId,
isShowCol: state.interfaceCol.isShowCol,
isRander: state.interfaceCol.isRander,
list: state.inter.list
}
},
{
fetchInterfaceColList,
fetchInterfaceCaseList,
fetchInterfaceList,
fetchCaseList,
setColData
}
)
@withRouter
export default class InterfaceColMenu extends Component {
static propTypes = {
match: PropTypes.object,
interfaceColList: PropTypes.array,
fetchInterfaceColList: PropTypes.func,
fetchInterfaceCaseList: PropTypes.func,
fetchInterfaceList: PropTypes.func,
fetchCaseList: PropTypes.func,
setColData: PropTypes.func,
history: PropTypes.object,
currColId: PropTypes.number,
currCaseId: PropTypes.number,
isShowCol: PropTypes.bool,
isRander: PropTypes.bool,
list: PropTypes.array
}
state = {
expandedKeys: [],
colModalType: '',
colModalVisible: false,
editColId: 0,
filterValue: '',
importInterVisible: false,
importInterIds: [],
importColId: 0
}
constructor(props) {
super(props)
}
async componentWillMount() {
const { isShowCol, currColId, currCaseId } = this.props;
const action = isShowCol ? 'col' : 'case';
const actionId = isShowCol ? currColId : currCaseId;
this.setState({ expandedKeys: [action + '_' + actionId] })
}
async componentWillReceiveProps(nextProps) {
const { currColId } = nextProps;
let expandedKeys = this.state.expandedKeys;
if (expandedKeys.indexOf('col_' + currColId) === -1) {
expandedKeys = expandedKeys.concat(['col_' + currColId])
}
this.setState({ expandedKeys })
}
addorEditCol = async () => {
const { colName: name, colDesc: desc } = this.form.getFieldsValue();
const { colModalType, editColId: col_id } = this.state;
const project_id = this.props.match.params.id;
let res = {};
if (colModalType === 'add') {
res = await axios.post('/api/col/add_col', { name, desc, project_id })
} else if (colModalType === 'edit') {
res = await axios.post('/api/col/up_col', { name, desc, col_id })
}
if (!res.data.errcode) {
this.setState({
colModalVisible: false
});
message.success(colModalType === 'edit' ? '修改集合成功' : '添加集合成功');
await this.props.fetchInterfaceColList(project_id);
} else {
message.error(res.data.errmsg);
}
}
onExpand = (keys) => {
this.setState({ expandedKeys: keys })
}
onSelect = (keys) => {
if (keys.length) {
const type = keys[0].split('_')[0];
const id = keys[0].split('_')[1];
const project_id = this.props.match.params.id
if (type === 'col') {
this.props.setColData({
isShowCol: true,
isRander: false,
currColId: +id
})
this.props.history.push('/project/' + project_id + '/interface/col/' + id)
} else {
this.props.setColData({
isShowCol: false,
isRander: false,
currCaseId: +id
})
this.props.history.push('/project/' + project_id + '/interface/case/' + id)
}
}
}
showDelColConfirm = (colId) => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试集合',
content: '温馨提示:该操作会删除该集合下所有测试用例,用例删除后无法恢复',
async onOk() {
const res = await axios.get('/api/col/del_col?col_id=' + colId)
if (!res.data.errcode) {
message.success('删除集合成功');
const result = await that.props.fetchInterfaceColList(that.props.match.params.id);
const nextColId = result.payload.data.data[0]._id;
that.props.history.push('/project/' + params.id + '/interface/col/' + nextColId);
} else {
message.error(res.data.errmsg);
}
}
});
}
// 复制测试集合
copyInterface = async (item) => {
if(this._copyInterfaceSign === true){
return ;
}
this._copyInterfaceSign = true;
const { desc, project_id, _id: col_id } = item;
let { name } = item;
name = `${name} copy`;
// 添加集合
const add_col_res = await axios.post('/api/col/add_col', { name, desc, project_id });
if (add_col_res.data.errcode) {
message.error(add_col_res.data.errmsg);
return;
}
const new_col_id = add_col_res.data.data._id;
// 克隆集合
const add_case_list_res = await axios.post('/api/col/clone_case_list', {
new_col_id,
col_id,
project_id
})
this._copyInterfaceSign = false;
if (add_case_list_res.data.errcode) {
message.error(add_case_list_res.data.errmsg);
return;
}
// 刷新接口列表
await this.props.fetchInterfaceColList(project_id);
this.props.setColData({ currColId: + new_col_id, isRander: true })
message.success('克隆测试集成功')
}
showNoDelColConfirm = () => {
confirm({
title: '此测试集合为最后一个集合',
content: '温馨提示:建议不要删除'
});
}
showDelCaseConfirm = (caseId) => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试用例',
content: '温馨提示:用例删除后无法恢复',
async onOk() {
const res = await axios.get('/api/col/del_case?caseid=' + caseId)
if (!res.data.errcode) {
message.success('删除用例成功');
// 如果删除当前选中 case切换路由到集合
if (+caseId === +that.props.currCaseId) {
that.props.history.push('/project/' + params.id + '/interface/col/')
} else {
that.props.fetchInterfaceColList(that.props.match.params.id);
that.props.setColData({ currColId: +that.props.currColId, isRander: true })
}
} else {
message.error(res.data.errmsg);
}
}
});
}
showColModal = (type, col) => {
const editCol = type === 'edit' ? { colName: col.name, colDesc: col.desc } : { colName: '', colDesc: '' };
this.setState({
colModalVisible: true,
colModalType: type || 'add',
editColId: col && col._id
})
this.form.setFieldsValue(editCol)
}
saveFormRef = (form) => {
this.form = form;
}
selectInterface = (importInterIds) => {
// console.log(importInterIds)
this.setState({ importInterIds })
}
showImportInterfaceModal = async (colId) => {
const projectId = this.props.match.params.id;
await this.props.fetchInterfaceList(projectId)
this.setState({ importInterVisible: true, importColId: colId })
}
handleImportOk = async () => {
const project_id = this.props.match.params.id;
const { importColId, importInterIds } = this.state;
const res = await axios.post('/api/col/add_case_list', {
interface_list: importInterIds,
col_id: importColId,
project_id
})
if (!res.data.errcode) {
this.setState({ importInterVisible: false })
message.success('导入集合成功');
await this.props.fetchInterfaceColList(project_id);
this.props.setColData({ currColId: +this.props.currColId, isRander: true })
} else {
message.error(res.data.errmsg);
}
}
handleImportCancel = () => {
this.setState({ importInterVisible: false })
}
filterCol = (e) => {
const value = e.target.value;
this.setState({ filterValue: value })
}
onDrop = async (e) => {
const projectId = this.props.match.params.id;
const dropColIndex = e.node.props.pos.split('-')[1];
const dropColId = this.props.interfaceColList[dropColIndex]._id;
const id = e.dragNode.props.eventKey;
const dragColIndex = e.dragNode.props.pos.split('-')[1];
const dragColId = this.props.interfaceColList[dragColIndex]._id;
if (id.indexOf('col') === -1 && dropColId !== dragColId) {
// if (dropColId !== dragColId) {
//
// }
// else {
// let caseList = this.props.interfaceColList[dropColIndex].caseList;
// const dropIndex = e.node.props.pos.split('-')[2];
// const dragIndex = e.dragNode.props.pos.split('-')[2];
// // caseList[dropIndex] = [caseList[dragIndex], caseList[dragIndex] = caseList[dropIndex]][0]
// let newArr = [].concat(caseList);
// newArr[dragIndex] = caseList[dropIndex];
// newArr[dropIndex] = caseList[dragIndex];
// let changes = [];
// newArr.forEach((item, index) => {
// changes.push({
// id: item._id,
// index: index
// })
// })
// axios.post('/api/col/up_col_index', changes).then()
// }
await axios.post('/api/col/up_case', { id: id.split('_')[1], col_id: dropColId });
this.props.fetchInterfaceColList(projectId);
this.props.setColData({ currColId: +this.props.currColId, isShowCol: true, isRander: true })
}
}
render() {
const { currColId, currCaseId, isShowCol } = this.props;
const { colModalType, colModalVisible, filterValue, importInterVisible } = this.state;
// const menu = (col) => {
// return (
// <Menu>
// <Menu.Item>
// <span onClick={() => this.showColModal('edit', col)}>修改集合</span>
// </Menu.Item>
// <Menu.Item>
// <span onClick={() => {
// this.showDelColConfirm(col._id)
// }}>删除集合</span>
// </Menu.Item>
// <Menu.Item>
// <span onClick={() => this.showImportInterface(col._id)}>导入接口</span>
// </Menu.Item>
// </Menu>
// )
// };
let isFilterCat = false;
// console.log();
// let caseList = this.props.interfaceColList.caseList;
// if(caseList&&caseList.length>1){
// caseList = caseList.sort((a,b)=>{
// return a.index-b.index;
// });
// this.props.interfaceColList.caseList = caseList;
// }
const list = this.props.interfaceColList.filter(col => {
if (col.name.indexOf(filterValue) !== -1) {
isFilterCat = true;
return true;
}
isFilterCat = false;
let caseList = col.caseList.filter(item => {
return item.casename.indexOf(filterValue) !== -1
})
return caseList.length > 0;
});
return (
<div>
<div className="interface-filter">
<Input placeholder="搜索测试集合" onChange={this.filterCol} />
<Tooltip placement="bottom" title="添加集合">
<Button type="primary" style={{ marginLeft: "16px" }} onClick={() => this.showColModal('add')} className="btn-filter" >添加集合</Button>
</Tooltip>
</div>
<Tree
className="col-list-tree"
expandedKeys={this.state.expandedKeys}
selectedKeys={[isShowCol ? 'col_' + currColId : 'case_' + currCaseId]}
onSelect={this.onSelect}
autoExpandParent
onExpand={this.onExpand}
ondragstart={() => { return false }}
>
{
list.map((col) => (
<TreeNode
key={'col_' + col._id}
title={
<div className="menu-title">
<span><Icon type="folder-open" style={{ marginRight: 5 }} /><span>{col.name}</span></span>
<div className="btns">
<Tooltip title="删除集合">
<Icon type='delete' style={{display:list.length > 1 ? '': 'none' }} className="interface-delete-icon" onClick={() => { this.showDelColConfirm(col._id) }} />
</Tooltip>
<Tooltip title="编辑集合">
<Icon type='edit' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.showColModal('edit', col) }} />
</Tooltip>
<Tooltip title="导入接口">
<Icon type='plus' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.showImportInterfaceModal(col._id) }} />
</Tooltip>
<Tooltip title="克隆集合">
<Icon type='copy' className="interface-delete-icon" onClick={(e) => { e.stopPropagation(); this.copyInterface(col) }} />
</Tooltip>
</div>
{/*<Dropdown overlay={menu(col)} trigger={['click']} onClick={e => e.stopPropagation()}>
<Icon className="opts-icon" type='ellipsis'/>
</Dropdown>*/}
</div>
}
>
{
col.caseList && col.caseList.filter((item) => {
if (isFilterCat) {
return true;
}
return item.casename.indexOf(filterValue) !== -1
}).sort((a, b) => {
return a.index - b.index;
}).map((interfaceCase) => (
<TreeNode
style={{ width: '100%' }}
key={'case_' + interfaceCase._id}
title={
<div className="menu-title" title={interfaceCase.casename}>
<span className="casename">{interfaceCase.casename}</span>
<Tooltip title="删除用例">
<Icon type='delete' className="case-delete-icon" onClick={(e) => { e.stopPropagation(); this.showDelCaseConfirm(interfaceCase._id) }} />
</Tooltip>
</div>
}
></TreeNode>
))
}
</TreeNode>
))
}
</Tree>
<ColModalForm
ref={this.saveFormRef}
type={colModalType}
visible={colModalVisible}
onCancel={() => { this.setState({ colModalVisible: false }) }}
onCreate={this.addorEditCol}
></ColModalForm>
<Modal
title="导入接口到集合"
visible={importInterVisible}
onOk={this.handleImportOk}
onCancel={this.handleImportCancel}
width={800}
>
<ImportInterface onChange={this.selectInterface} list={this.props.list} />
</Modal>
</div>
)
}
}

View File

@ -50,7 +50,6 @@ class InterfaceEdit extends Component {
if (result.data.errcode === 0) {
this.props.updateInterfaceData(params);
message.success('保存成功');
this.props.switchToView()
} else {
message.error(result.data.errmsg)
}

View File

@ -214,6 +214,7 @@ class InterfaceEditForm extends Component {
values.req_body_form = []
}
this.props.onSubmit(values)
EditFormContext.props.changeEditStatus(false);
}
});
}
@ -725,7 +726,7 @@ class InterfaceEditForm extends Component {
<Row className={'interface-edit-item ' + this.state.hideTabs.req.query}>
<Col>
<EasyDragSort data={this.props.form.getFieldValue('req_query')} onChange={this.handleDragMove('req_query')} >
<EasyDragSort data={()=>this.props.form.getFieldValue('req_query')} onChange={this.handleDragMove('req_query')} >
{QueryList}
</EasyDragSort>
</Col>
@ -740,7 +741,7 @@ class InterfaceEditForm extends Component {
<Row className={'interface-edit-item ' + this.state.hideTabs.req.headers}>
<Col>
<EasyDragSort data={this.props.form.getFieldValue('req_headers')} onChange={this.handleDragMove('req_headers')} >
<EasyDragSort data={()=>this.props.form.getFieldValue('req_headers')} onChange={this.handleDragMove('req_headers')} >
{headerList}
</EasyDragSort>
</Col>
@ -773,7 +774,7 @@ class InterfaceEditForm extends Component {
</Col>
</Row>
<EasyDragSort data={this.props.form.getFieldValue('req_body_form')} onChange={this.handleDragMove('req_body_form')} >
<EasyDragSort data={() => this.props.form.getFieldValue('req_body_form')} onChange={this.handleDragMove('req_body_form')} >
{requestBodyList}
</EasyDragSort>
@ -798,7 +799,7 @@ class InterfaceEditForm extends Component {
</Col>
</Row>
{this.props.form.getFieldValue('req_body_type') === 'file' ?
{this.props.form.getFieldValue('req_body_type') === 'file' && this.state.hideTabs.req.body !== 'hide' ?
<Row className="interface-edit-item" >
<Col className="interface-edit-item-other-body">
{getFieldDecorator('req_body_other', { initialValue: this.state.req_body_other })(
@ -811,7 +812,7 @@ class InterfaceEditForm extends Component {
:
null
}
{this.props.form.getFieldValue('req_body_type') === 'raw' ?
{this.props.form.getFieldValue('req_body_type') === 'raw' && this.state.hideTabs.req.body !== 'hide'?
<Row>
<Col>
{getFieldDecorator('req_body_other', { initialValue: this.state.req_body_other })(

View File

@ -11,5 +11,7 @@ module.exports = {
name: 'statistics'
},{
name: 'export-data'
}, {
name: 'api-history'
}]
}

View File

@ -0,0 +1,10 @@
import InterfaceHistory from './client/InterfaceHistory.js';
module.exports = function(){
this.bindHook('interface_tab', function(tabs){
tabs.advMock = {
name: '历史',
component: InterfaceHistory
}
})
}

View File

@ -0,0 +1,38 @@
import React, { Component } from 'react'
import axios from 'axios'
import PropTypes from 'prop-types'
//import { Form, Switch, Button, message, Icon, Tooltip, Radio } from 'antd';
class AdvMock extends Component {
// static propTypes = {
// form: PropTypes.object,
// match: PropTypes.object
// }
constructor(props) {
super(props);
this.state = {
}
}
handleSubmit = () => {
}
componentWillMount() {
}
render() {
return <div >
Hello, World.
</div>
}
}
module.exports = AdvMock;

View File

@ -0,0 +1,4 @@
module.exports = {
server: true,
client: true
}

View File

@ -0,0 +1,3 @@
module.exports = function(){
}

File diff suppressed because it is too large Load Diff

View File

@ -1,114 +1,114 @@
const yapi = require('../yapi.js');
const baseModel = require('./base.js');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
class interfaceCase extends baseModel {
getName() {
return 'interface_case';
}
getSchema() {
return {
casename: { type: String, required: true },
uid: { type: Number, required: true },
col_id: { type: Number, required: true },
index: { type: Number, default: 0 },
project_id: { type: Number, required: true },
interface_id: { type: Number, required: true },
add_time: Number,
up_time: Number,
case_env: { type: String },
req_params: [{
name: String, value: String
}],
req_headers: [{
name: String, value: String
}],
req_query: [{
name: String, value: String, enable: {type: Boolean, default: true}
}],
req_body_form: [{
name: String, value: String, enable: {type: Boolean, default: true}
}],
req_body_other: String,
test_res_body: String,
test_status: {type: String, enum: ['ok', 'invalid', 'error', '']},
test_res_header: Schema.Types.Mixed,
mock_verify: {type: Boolean, default: false},
enable_script: {type: Boolean, default: false},
test_script: String
};
}
save(data) {
let m = new this.model(data);
return m.save();
}
//获取全部测试接口信息
getInterfaceCaseListCount() {
return this.model.count({});
}
get(id) {
return this.model.findOne({
_id: id
}).exec();
}
list(col_id, select) {
select = select || 'casename uid col_id _id index'
if (select === 'all') {
return this.model.find({
col_id: col_id
}).exec();
}
return this.model.find({
col_id: col_id
}).select("casename uid col_id _id index").exec();
}
del(id) {
return this.model.remove({
_id: id
});
}
delByProjectId(id) {
return this.model.remove({
project_id: id
})
}
delByInterfaceId(id){
return this.model.remove({
interface_id: id
})
}
delByCol(id) {
return this.model.remove({
col_id: id
})
}
up(id, data) {
data.up_time = yapi.commons.time()
return this.model.update(
{ _id: id },
data
);
}
upCaseIndex(id, index) {
return this.model.update({
_id: id
}, {
index: index
})
}
}
module.exports = interfaceCase;
const yapi = require('../yapi.js');
const baseModel = require('./base.js');
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
class interfaceCase extends baseModel {
getName() {
return 'interface_case';
}
getSchema() {
return {
casename: { type: String, required: true },
uid: { type: Number, required: true },
col_id: { type: Number, required: true },
index: { type: Number, default: 0 },
project_id: { type: Number, required: true },
interface_id: { type: Number, required: true },
add_time: Number,
up_time: Number,
case_env: { type: String },
req_params: [{
name: String, value: String
}],
req_headers: [{
name: String, value: String
}],
req_query: [{
name: String, value: String, enable: { type: Boolean, default: true }
}],
req_body_form: [{
name: String, value: String, enable: { type: Boolean, default: true }
}],
req_body_other: String,
test_res_body: String,
test_status: { type: String, enum: ['ok', 'invalid', 'error', ''] },
test_res_header: Schema.Types.Mixed,
mock_verify: { type: Boolean, default: false },
enable_script: { type: Boolean, default: false },
test_script: String
};
}
save(data) {
let m = new this.model(data);
return m.save();
}
//获取全部测试接口信息
getInterfaceCaseListCount() {
return this.model.count({});
}
get(id) {
return this.model.findOne({
_id: id
}).exec();
}
list(col_id, select) {
select = select || 'casename uid col_id _id index interface_id'
if (select === 'all') {
return this.model.find({
col_id: col_id
}).exec();
}
return this.model.find({
col_id: col_id
}).select("casename uid col_id _id index interface_id").exec();
}
del(id) {
return this.model.remove({
_id: id
});
}
delByProjectId(id) {
return this.model.remove({
project_id: id
})
}
delByInterfaceId(id) {
return this.model.remove({
interface_id: id
})
}
delByCol(id) {
return this.model.remove({
col_id: id
})
}
up(id, data) {
data.up_time = yapi.commons.time()
return this.model.update(
{ _id: id },
data
);
}
upCaseIndex(id, index) {
return this.model.update({
_id: id
}, {
index: index
})
}
}
module.exports = interfaceCase;

View File

@ -328,6 +328,11 @@ let routerConfig = {
path: 'add_case_list',
method: 'post'
}, {
action: 'cloneCaseList',
path: 'clone_case_list',
method: 'post'
}, {
action: "list",
path: "list",
@ -415,7 +420,7 @@ let routerConfig = {
path: "http/code",
method: "post"
}
]
]
}
let pluginsRouterPath = [];
@ -430,7 +435,7 @@ function addPluginRouter(config) {
throw new Error('Plugin Route path conflict, please try rename the path')
}
pluginsRouterPath.push(routerPath);
createAction(router, "/api", config.controller, config.action, routerPath, method,false);
createAction(router, "/api", config.controller, config.action, routerPath, method, false);
}
yapi.emitHookSync('add_router', addPluginRouter);

View File

@ -1 +1,21 @@
window.WEBPACK_ASSETS = {"index.js":{"js":"index@773f87ecdd1b72f9076c.js","css":"index@773f87ecdd1b72f9076c.css"},"lib":{"js":"lib.min.js"},"lib2":{"js":"lib2.min.js"},"manifest":{"js":"manifest.min.js"}}
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
window.WEBPACK_ASSETS = {"index.js":{"js":"index@cd86ca67ec83cf50bf7f.js","css":"index@cd86ca67ec83cf50bf7f.css"},"lib":{"js":"lib@440c1fc133fa194fb5a7.js"},"lib2":{"js":"lib2@2b54ffd5cfd140edb818.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
=======
<<<<<<< HEAD
window.WEBPACK_ASSETS = {"index.js":{"js":"index@5d0172e1e8d025077c6d.js","css":"index@5d0172e1e8d025077c6d.css"},"lib":{"js":"lib@9f81ac11019b0fb44526.js"},"lib2":{"js":"lib2@d45c8b21cd152647ed17.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@1c90318cffcd9226f4ca.js","css":"index@1c90318cffcd9226f4ca.css"},"lib":{"js":"lib@b527656c385f817c6379.js"},"lib2":{"js":"lib2@89ae3d589a0377410eb8.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
>>>>>>> dev-1.2.0
>>>>>>> dev
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@2749a62173cb051bd43a.js","css":"index@2749a62173cb051bd43a.css"},"lib":{"js":"lib@5ca86296ff97fbf15aa9.js"},"lib2":{"js":"lib2@4e246d55b157fd90cbd6.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
>>>>>>> dev
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@61638219be6877d986cc.js","css":"index@61638219be6877d986cc.css"},"lib":{"js":"lib@d8397632ca19f2cbfc57.js"},"lib2":{"js":"lib2@7552d509ada57b7aecab.js"},"manifest":{"js":"manifest@b67af9f8b578904e66c5.js"}}
>>>>>>> dev
=======
window.WEBPACK_ASSETS = {"index.js":{"js":"index@773f87ecdd1b72f9076c.js","css":"index@773f87ecdd1b72f9076c.css"},"lib":{"js":"lib.min.js"},"lib2":{"js":"lib2.min.js"},"manifest":{"js":"manifest.min.js"}}
>>>>>>> e0633fd4cd88abe7a577250d3fb4a4d4a47e497f

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -85,20 +85,22 @@ module.exports = {
'redux-promise',
'react-router',
'react-router-dom',
'prop-types',
'axios',
'moment',
'prop-types',
'react-dnd-html5-backend',
'react-dnd',
'reactabular-table',
'reactabular-dnd',
'table-resolver'
'table-resolver',
'recharts'
],
lib2: [
'brace',
'mockjs',
'json5',
'url'
'url',
'wangeditor',
'axios',
'moment'
]
}
},