This commit is contained in:
2024-03-01 20:28:14 +08:00
commit 076c21dc36
491 changed files with 84482 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Row, Col, Tabs } from 'antd';
const TabPane = Tabs.TabPane;
function jsonFormat(json) {
// console.log('json',json)
if (json && typeof json === 'object') {
return JSON.stringify(json, null, ' ');
}
return json;
}
const CaseReport = function(props) {
let params = jsonFormat(props.data);
let headers = jsonFormat(props.headers, null, ' ');
let res_header = jsonFormat(props.res_header, null, ' ');
let res_body = jsonFormat(props.res_body);
let httpCode = props.status;
let validRes;
if (props.validRes && Array.isArray(props.validRes)) {
validRes = props.validRes.map((item, index) => {
return <div key={index}>{item.message}</div>;
});
}
return (
<div className="report">
<Tabs defaultActiveKey="request">
<TabPane className="case-report-pane" tab="Request" key="request">
<Row className="case-report">
<Col className="case-report-title" span="6">
Url
</Col>
<Col span="18">{props.url}</Col>
</Row>
{props.query ? (
<Row className="case-report">
<Col className="case-report-title" span="6">
Query
</Col>
<Col span="18">{props.query}</Col>
</Row>
) : null}
{props.headers ? (
<Row className="case-report">
<Col className="case-report-title" span="6">
Headers
</Col>
<Col span="18">
<pre>{headers}</pre>
</Col>
</Row>
) : null}
{params ? (
<Row className="case-report">
<Col className="case-report-title" span="6">
Body
</Col>
<Col span="18">
<pre style={{ whiteSpace: 'pre-wrap' }}>{params}</pre>
</Col>
</Row>
) : null}
</TabPane>
<TabPane className="case-report-pane" tab="Response" key="response">
<Row className="case-report">
<Col className="case-report-title" span="6">
HttpCode
</Col>
<Col span="18">
<pre>{httpCode}</pre>
</Col>
</Row>
{props.res_header ? (
<Row className="case-report">
<Col className="case-report-title" span="6">
Headers
</Col>
<Col span="18">
<pre>{res_header}</pre>
</Col>
</Row>
) : null}
{props.res_body ? (
<Row className="case-report">
<Col className="case-report-title" span="6">
Body
</Col>
<Col span="18">
<pre>{res_body}</pre>
</Col>
</Row>
) : null}
</TabPane>
<TabPane className="case-report-pane" tab="验证结果" key="valid">
{props.validRes ? (
<Row className="case-report">
<Col className="case-report-title" span="6">
验证结果
</Col>
<Col span="18"><pre>
{validRes}
</pre></Col>
</Row>
) : null}
</TabPane>
</Tabs>
</div>
);
};
CaseReport.propTypes = {
url: PropTypes.string,
data: PropTypes.any,
headers: PropTypes.object,
res_header: PropTypes.object,
res_body: PropTypes.any,
query: PropTypes.string,
validRes: PropTypes.array,
status: PropTypes.number
};
export default CaseReport;

View File

@@ -0,0 +1,241 @@
import React, { PureComponent as Component } from 'react';
import PropTypes from 'prop-types';
import { Table, Select, Tooltip, Icon } from 'antd';
import variable from '../../../../constants/variable';
import { connect } from 'react-redux';
const Option = Select.Option;
import { fetchInterfaceListMenu } from '../../../../reducer/modules/interface.js';
@connect(
state => {
return {
projectList: state.project.projectList,
list: state.inter.list
};
},
{
fetchInterfaceListMenu
}
)
export default class ImportInterface extends Component {
constructor(props) {
super(props);
}
state = {
selectedRowKeys: [],
categoryCount: {},
project: this.props.currProjectId
};
static propTypes = {
list: PropTypes.array,
selectInterface: PropTypes.func,
projectList: PropTypes.array,
currProjectId: PropTypes.string,
fetchInterfaceListMenu: PropTypes.func
};
async componentDidMount() {
// console.log(this.props.currProjectId)
await this.props.fetchInterfaceListMenu(this.props.currProjectId);
}
// 切换项目
onChange = async val => {
this.setState({
project: val,
selectedRowKeys: [],
categoryCount: {}
});
await this.props.fetchInterfaceListMenu(val);
};
render() {
const { list, projectList } = this.props;
// const { selectedRowKeys } = this.state;
const data = list.map(item => {
return {
key: 'category_' + item._id,
title: item.name,
isCategory: true,
children: item.list
? item.list.map(e => {
e.key = e._id;
e.categoryKey = 'category_' + item._id;
e.categoryLength = item.list.length;
return e;
})
: []
};
});
const self = this;
const rowSelection = {
// onChange: (selectedRowKeys) => {
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
// if (selectedRows.isCategory) {
// const selectedRowKeys = selectedRows.children.map(item => item._id)
// this.setState({ selectedRowKeys })
// }
// this.props.onChange(selectedRowKeys.filter(id => ('' + id).indexOf('category') === -1));
// },
onSelect: (record, selected) => {
// console.log(record, selected, selectedRows);
const oldSelecteds = self.state.selectedRowKeys;
const categoryCount = self.state.categoryCount;
const categoryKey = record.categoryKey;
const categoryLength = record.categoryLength;
let selectedRowKeys = [];
if (record.isCategory) {
selectedRowKeys = record.children.map(item => item._id).concat(record.key);
if (selected) {
selectedRowKeys = selectedRowKeys
.filter(id => oldSelecteds.indexOf(id) === -1)
.concat(oldSelecteds);
categoryCount[categoryKey] = categoryLength;
} else {
selectedRowKeys = oldSelecteds.filter(id => selectedRowKeys.indexOf(id) === -1);
categoryCount[categoryKey] = 0;
}
} else {
if (selected) {
selectedRowKeys = oldSelecteds.concat(record._id);
if (categoryCount[categoryKey]) {
categoryCount[categoryKey] += 1;
} else {
categoryCount[categoryKey] = 1;
}
if (categoryCount[categoryKey] === record.categoryLength) {
selectedRowKeys.push(categoryKey);
}
} else {
selectedRowKeys = oldSelecteds.filter(id => id !== record._id);
if (categoryCount[categoryKey]) {
categoryCount[categoryKey] -= 1;
}
selectedRowKeys = selectedRowKeys.filter(id => id !== categoryKey);
}
}
self.setState({ selectedRowKeys, categoryCount });
self.props.selectInterface(
selectedRowKeys.filter(id => ('' + id).indexOf('category') === -1),
self.state.project
);
},
onSelectAll: selected => {
// console.log(selected, selectedRows, changeRows);
let selectedRowKeys = [];
let categoryCount = self.state.categoryCount;
if (selected) {
data.forEach(item => {
if (item.children) {
categoryCount['category_' + item._id] = item.children.length;
selectedRowKeys = selectedRowKeys.concat(item.children.map(item => item._id));
}
});
selectedRowKeys = selectedRowKeys.concat(data.map(item => item.key));
} else {
categoryCount = {};
selectedRowKeys = [];
}
self.setState({ selectedRowKeys, categoryCount });
self.props.selectInterface(
selectedRowKeys.filter(id => ('' + id).indexOf('category') === -1),
self.state.project
);
},
selectedRowKeys: self.state.selectedRowKeys
};
const columns = [
{
title: '接口名称',
dataIndex: 'title',
width: '30%'
},
{
title: '接口路径',
dataIndex: 'path',
width: '40%'
},
{
title: '请求方法',
dataIndex: 'method',
render: item => {
let methodColor = variable.METHOD_COLOR[item ? item.toLowerCase() : 'get'];
return (
<span
style={{
color: methodColor.color,
backgroundColor: methodColor.bac,
borderRadius: 4
}}
className="colValue"
>
{item}
</span>
);
}
},
{
title: (
<span>
状态{' '}
<Tooltip title="筛选满足条件的接口集合">
<Icon type="question-circle-o" />
</Tooltip>
</span>
),
dataIndex: 'status',
render: text => {
return (
text &&
(text === 'done' ? (
<span className="tag-status done">已完成</span>
) : (
<span className="tag-status undone">未完成</span>
))
);
},
filters: [
{
text: '已完成',
value: 'done'
},
{
text: '未完成',
value: 'undone'
}
],
onFilter: (value, record) => {
let arr = record.children.filter(item => {
return item.status.indexOf(value) === 0;
});
return arr.length > 0;
// record.status.indexOf(value) === 0
}
}
];
return (
<div>
<div className="select-project">
<span>选择要导入的项目 </span>
<Select value={this.state.project} style={{ width: 200 }} onChange={this.onChange}>
{projectList.map(item => {
return item.projectname ? (
''
) : (
<Option value={`${item._id}`} key={item._id}>
{item.name}
</Option>
);
})}
</Select>
</div>
<Table columns={columns} rowSelection={rowSelection} dataSource={data} pagination={false} />
</div>
);
}
}

View File

@@ -0,0 +1,235 @@
import React, { PureComponent as Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
import { Link } from 'react-router-dom';
import axios from 'axios';
import { message, Tooltip, Input } from 'antd';
import { getEnv } from '../../../../reducer/modules/project';
import {
fetchInterfaceColList,
setColData,
fetchCaseData,
fetchCaseList
} from '../../../../reducer/modules/interfaceCol';
import { Postman } from '../../../../components';
import './InterfaceCaseContent.scss';
@connect(
state => {
return {
interfaceColList: state.interfaceCol.interfaceColList,
currColId: state.interfaceCol.currColId,
currCaseId: state.interfaceCol.currCaseId,
currCase: state.interfaceCol.currCase,
isShowCol: state.interfaceCol.isShowCol,
currProject: state.project.currProject,
projectEnv: state.project.projectEnv,
curUid: state.user.uid
};
},
{
fetchInterfaceColList,
fetchCaseData,
setColData,
fetchCaseList,
getEnv
}
)
@withRouter
export default class InterfaceCaseContent extends Component {
static propTypes = {
match: PropTypes.object,
interfaceColList: PropTypes.array,
fetchInterfaceColList: PropTypes.func,
fetchCaseData: PropTypes.func,
setColData: PropTypes.func,
fetchCaseList: PropTypes.func,
history: PropTypes.object,
currColId: PropTypes.number,
currCaseId: PropTypes.number,
currCase: PropTypes.object,
isShowCol: PropTypes.bool,
currProject: PropTypes.object,
getEnv: PropTypes.func,
projectEnv: PropTypes.object,
curUid: PropTypes.number
};
state = {
isEditingCasename: true,
editCasename: ''
};
constructor(props) {
super(props);
}
getColId(colList, currCaseId) {
let currColId = 0;
colList.forEach(col => {
col.caseList.forEach(caseItem => {
if (+caseItem._id === +currCaseId) {
currColId = col._id;
}
});
});
return currColId;
}
async UNSAFE_componentWillMount() {
const result = await this.props.fetchInterfaceColList(this.props.match.params.id);
let { currCaseId } = this.props;
const params = this.props.match.params;
const { actionId } = params;
currCaseId = +actionId || +currCaseId || result.payload.data.data[0].caseList[0]._id;
let currColId = this.getColId(result.payload.data.data, currCaseId);
// this.props.history.push('/project/' + params.id + '/interface/case/' + currCaseId);
await this.props.fetchCaseData(currCaseId);
this.props.setColData({ currCaseId: +currCaseId, currColId, isShowCol: false });
// 获取当前case 下的环境变量
await this.props.getEnv(this.props.currCase.project_id);
// await this.getCurrEnv()
this.setState({ editCasename: this.props.currCase.casename });
}
async UNSAFE_componentWillReceiveProps(nextProps) {
const oldCaseId = this.props.match.params.actionId;
const newCaseId = nextProps.match.params.actionId;
const { interfaceColList } = nextProps;
let currColId = this.getColId(interfaceColList, newCaseId);
if (oldCaseId !== newCaseId) {
await this.props.fetchCaseData(newCaseId);
this.props.setColData({ currCaseId: +newCaseId, currColId, isShowCol: false });
await this.props.getEnv(this.props.currCase.project_id);
// await this.getCurrEnv()
this.setState({ editCasename: this.props.currCase.casename });
}
}
savePostmanRef = postman => {
this.postman = postman;
};
updateCase = async () => {
const {
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;
const { _id: id } = this.props.currCase;
let params = {
id,
casename,
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
};
const res = await axios.post('/api/col/up_case', params);
if (this.props.currCase.casename !== casename) {
this.props.fetchInterfaceColList(this.props.match.params.id);
}
if (res.data.errcode) {
message.error(res.data.errmsg);
} else {
message.success('更新成功');
this.props.fetchCaseData(id);
}
};
triggerEditCasename = () => {
this.setState({
isEditingCasename: true,
editCasename: this.props.currCase.casename
});
};
cancelEditCasename = () => {
this.setState({
isEditingCasename: false,
editCasename: this.props.currCase.casename
});
};
render() {
const { currCase, currProject, projectEnv } = this.props;
const { isEditingCasename, editCasename } = this.state;
const data = Object.assign(
{},
currCase,
{
env: projectEnv.env,
pre_script: currProject.pre_script,
after_script: currProject.after_script
},
{ _id: currCase._id }
);
return (
<div style={{ padding: '6px 0' }} className="case-content">
<div className="case-title">
{!isEditingCasename && (
<Tooltip title="点击编辑" placement="bottom">
<div className="case-name" onClick={this.triggerEditCasename}>
{currCase.casename}
</div>
</Tooltip>
)}
{isEditingCasename && (
<div className="edit-case-name">
<Input
value={editCasename}
onChange={e => this.setState({ editCasename: e.target.value })}
style={{ fontSize: 18 }}
/>
</div>
)}
<span className="inter-link" style={{ margin: '0px 8px 0px 6px', fontSize: 12 }}>
<Link
className="text"
to={`/project/${currCase.project_id}/interface/api/${currCase.interface_id}`}
>
对应接口
</Link>
</span>
</div>
<div>
{Object.keys(currCase).length > 0 && (
<Postman
data={data}
type="case"
saveTip="更新保存修改"
save={this.updateCase}
ref={this.savePostmanRef}
interfaceId={currCase.interface_id}
projectId={currCase.project_id}
curUid={this.props.curUid}
/>
)}
</div>
</div>
);
}
}

View File

@@ -0,0 +1,33 @@
.case-content {
padding: 6px 0;
.case-title {
display: flex;
.case-name {
margin-left: 8px;
border-radius: 4px;
border: 1px solid transparent;
padding: 0 5px;
background-color: #eee;
font-size: 20px;
flex-grow: 1;
cursor: pointer;
}
.case-name:hover {
color: rgba(0,0,0,.65);
border: 1px solid #d9d9d9;
}
.edit-case-name {
margin-left: 8px;
display: flex;
flex-grow: 1;
}
.inter-link {
flex-basis: 50px;
position: relative;
}
.inter-link .text {
position: absolute;
bottom: 4px;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,630 @@
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,
fetchCaseData
} from '../../../../reducer/modules/interfaceCol';
import { fetchProjectList } from '../../../../reducer/modules/project';
import axios from 'axios';
import ImportInterface from './ImportInterface';
import { Input, Icon, Button, Modal, message, Tooltip, Tree, Form } from 'antd';
import { arrayChangeIndex } from '../../../../common.js';
import _ from 'underscore'
const TreeNode = Tree.TreeNode;
const FormItem = Form.Item;
const confirm = Modal.confirm;
const headHeight = 240; // menu顶部到网页顶部部分的高度
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,
currCase: state.interfaceCol.currCase,
isRander: state.interfaceCol.isRander,
currCaseId: state.interfaceCol.currCaseId,
// list: state.inter.list,
// 当前项目的信息
curProject: state.project.currProject
// projectList: state.project.projectList
};
},
{
fetchInterfaceColList,
fetchInterfaceCaseList,
fetchCaseData,
// fetchInterfaceListMenu,
fetchCaseList,
setColData,
fetchProjectList
}
)
@withRouter
export default class InterfaceColMenu extends Component {
static propTypes = {
match: PropTypes.object,
interfaceColList: PropTypes.array,
fetchInterfaceColList: PropTypes.func,
fetchInterfaceCaseList: PropTypes.func,
// fetchInterfaceListMenu: PropTypes.func,
fetchCaseList: PropTypes.func,
fetchCaseData: PropTypes.func,
setColData: PropTypes.func,
currCaseId: PropTypes.number,
history: PropTypes.object,
isRander: PropTypes.bool,
// list: PropTypes.array,
router: PropTypes.object,
currCase: PropTypes.object,
curProject: PropTypes.object,
fetchProjectList: PropTypes.func
// projectList: PropTypes.array
};
state = {
colModalType: '',
colModalVisible: false,
editColId: 0,
filterValue: '',
importInterVisible: false,
importInterIds: [],
importColId: 0,
expands: null,
list: [],
delIcon: null,
selectedProject: null
};
constructor(props) {
super(props);
}
UNSAFE_componentWillMount() {
this.getList();
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.interfaceColList !== nextProps.interfaceColList) {
this.setState({
list: nextProps.interfaceColList
});
}
}
async getList() {
let r = await this.props.fetchInterfaceColList(this.props.match.params.id);
this.setState({
list: r.payload.data.data
});
return r;
}
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);
this.getList();
} else {
message.error(res.data.errmsg);
}
};
onExpand = keys => {
this.setState({ expands: keys });
};
onSelect = _.debounce(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({
isRander: false
});
this.props.history.push('/project/' + project_id + '/interface/col/' + id);
} else {
this.props.setColData({
isRander: false
});
this.props.history.push('/project/' + project_id + '/interface/case/' + id);
}
}
this.setState({
expands: null
});
}, 500);
showDelColConfirm = colId => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试集合',
content: '温馨提示:该操作会删除该集合下所有测试用例,用例删除后无法恢复',
okText: '确认',
cancelText: '取消',
async onOk() {
const res = await axios.get('/api/col/del_col?col_id=' + colId);
if (!res.data.errcode) {
message.success('删除集合成功');
const result = await that.getList();
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.getList();
this.props.setColData({ isRander: true });
message.success('克隆测试集成功');
};
showNoDelColConfirm = () => {
confirm({
title: '此测试集合为最后一个集合',
content: '温馨提示:建议不要删除'
});
};
caseCopy = async caseId=> {
let that = this;
let caseData = await that.props.fetchCaseData(caseId);
let data = caseData.payload.data.data;
data = JSON.parse(JSON.stringify(data));
data.casename=`${data.casename}_copy`
delete data._id
const res = await axios.post('/api/col/add_case',data);
if (!res.data.errcode) {
message.success('克隆用例成功');
let colId = res.data.data.col_id;
let projectId=res.data.data.project_id;
await this.getList();
this.props.history.push('/project/' + projectId + '/interface/col/' + colId);
this.setState({
visible: false
});
} else {
message.error(res.data.errmsg);
}
};
showDelCaseConfirm = caseId => {
let that = this;
const params = this.props.match.params;
confirm({
title: '您确认删除此测试用例',
content: '温馨提示:用例删除后无法恢复',
okText: '确认',
cancelText: '取消',
async onOk() {
const res = await axios.get('/api/col/del_case?caseid=' + caseId);
if (!res.data.errcode) {
message.success('删除用例成功');
that.getList();
// 如果删除当前选中 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({ 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, selectedProject) => {
this.setState({ importInterIds, selectedProject });
};
showImportInterfaceModal = async colId => {
// const projectId = this.props.match.params.id;
// console.log('project', this.props.curProject)
const groupId = this.props.curProject.group_id;
await this.props.fetchProjectList(groupId);
// await this.props.fetchInterfaceListMenu(projectId)
this.setState({ importInterVisible: true, importColId: colId });
};
handleImportOk = async () => {
const project_id = this.state.selectedProject || 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.getList();
this.props.setColData({ isRander: true });
} else {
message.error(res.data.errmsg);
}
};
handleImportCancel = () => {
this.setState({ importInterVisible: false });
};
filterCol = e => {
const value = e.target.value;
// console.log('list', this.props.interfaceColList);
// const newList = produce(this.props.interfaceColList, draftList => {})
// console.log('newList',newList);
this.setState({
filterValue: value,
list: JSON.parse(JSON.stringify(this.props.interfaceColList))
// list: newList
});
};
onDrop = async e => {
// const projectId = this.props.match.params.id;
const { interfaceColList } = this.props;
const dropColIndex = e.node.props.pos.split('-')[1];
const dropColId = interfaceColList[dropColIndex]._id;
const id = e.dragNode.props.eventKey;
const dragColIndex = e.dragNode.props.pos.split('-')[1];
const dragColId = interfaceColList[dragColIndex]._id;
const dropPos = e.node.props.pos.split('-');
const dropIndex = Number(dropPos[dropPos.length - 1]);
const dragPos = e.dragNode.props.pos.split('-');
const dragIndex = Number(dragPos[dragPos.length - 1]);
if (id.indexOf('col') === -1) {
if (dropColId === dragColId) {
// 同一个测试集合下的接口交换顺序
let caseList = interfaceColList[dropColIndex].caseList;
let changes = arrayChangeIndex(caseList, dragIndex, dropIndex);
axios.post('/api/col/up_case_index', changes).then();
}
await axios.post('/api/col/up_case', { id: id.split('_')[1], col_id: dropColId });
// this.props.fetchInterfaceColList(projectId);
this.getList();
this.props.setColData({ isRander: true });
} else {
let changes = arrayChangeIndex(interfaceColList, dragIndex, dropIndex);
axios.post('/api/col/up_col_index', changes).then();
this.getList();
}
};
enterItem = id => {
this.setState({ delIcon: id });
};
leaveItem = () => {
this.setState({ delIcon: null });
};
render() {
// const { currColId, currCaseId, isShowCol } = this.props;
const { colModalType, colModalVisible, importInterVisible } = this.state;
const currProjectId = this.props.match.params.id;
// 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>
// )
// };
const defaultExpandedKeys = () => {
const { router, currCase, interfaceColList } = this.props,
rNull = { expands: [], selects: [] };
if (interfaceColList.length === 0) {
return rNull;
}
if (router) {
if (router.params.action === 'case') {
if (!currCase || !currCase._id) {
return rNull;
}
return {
expands: this.state.expands ? this.state.expands : ['col_' + currCase.col_id],
selects: ['case_' + currCase._id + '']
};
} else {
let col_id = router.params.actionId;
return {
expands: this.state.expands ? this.state.expands : ['col_' + col_id],
selects: ['col_' + col_id]
};
}
} else {
return {
expands: this.state.expands ? this.state.expands : ['col_' + interfaceColList[0]._id],
selects: ['col_' + interfaceColList[0]._id]
};
}
};
const itemInterfaceColCreate = interfaceCase => {
return (
<TreeNode
style={{ width: '100%' }}
key={'case_' + interfaceCase._id}
title={
<div
className="menu-title"
onMouseEnter={() => this.enterItem(interfaceCase._id)}
onMouseLeave={this.leaveItem}
title={interfaceCase.casename}
>
<span className="casename">{interfaceCase.casename}</span>
<div className="btns">
<Tooltip title="删除用例">
<Icon
type="delete"
className="interface-delete-icon"
onClick={e => {
e.stopPropagation();
this.showDelCaseConfirm(interfaceCase._id);
}}
style={{ display: this.state.delIcon == interfaceCase._id ? 'block' : 'none' }}
/>
</Tooltip>
<Tooltip title="克隆用例">
<Icon
type="copy"
className="interface-delete-icon"
onClick={e => {
e.stopPropagation();
this.caseCopy(interfaceCase._id);
}}
style={{ display: this.state.delIcon == interfaceCase._id ? 'block' : 'none' }}
/>
</Tooltip>
</div>
</div>
}
/>
);
};
let currentKes = defaultExpandedKeys();
// console.log('currentKey', currentKes)
let list = this.state.list;
if (this.state.filterValue) {
let arr = [];
list = list.filter(item => {
item.caseList = item.caseList.filter(inter => {
if (inter.casename.indexOf(this.state.filterValue) === -1
&& inter.path.indexOf(this.state.filterValue) === -1
) {
return false;
}
return true;
});
arr.push('col_' + item._id);
return true;
});
// console.log('arr', arr);
if (arr.length > 0) {
currentKes.expands = arr;
}
}
// console.log('list', list);
// console.log('currentKey', currentKes)
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>
<div className="tree-wrapper" style={{ maxHeight: parseInt(document.body.clientHeight) - headHeight + 'px'}}>
<Tree
className="col-list-tree"
defaultExpandedKeys={currentKes.expands}
defaultSelectedKeys={currentKes.selects}
expandedKeys={currentKes.expands}
selectedKeys={currentKes.selects}
onSelect={this.onSelect}
autoExpandParent
draggable
onExpand={this.onExpand}
onDrop={this.onDrop}
>
{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.map(itemInterfaceColCreate)}
</TreeNode>
))}
</Tree>
</div>
<ColModalForm
ref={this.saveFormRef}
type={colModalType}
visible={colModalVisible}
onCancel={() => {
this.setState({ colModalVisible: false });
}}
onCreate={this.addorEditCol}
/>
<Modal
title="导入接口到集合"
visible={importInterVisible}
onOk={this.handleImportOk}
onCancel={this.handleImportCancel}
className="import-case-modal"
width={800}
>
<ImportInterface currProjectId={currProjectId} selectInterface={this.selectInterface} />
</Modal>
</div>
);
}
}

View File

@@ -0,0 +1,186 @@
.col-list-tree {
line-height: 25px;
.ant-tree-node-content-wrapper {
width: calc(100% - 28px);
}
.opts-icon,
.case-delete-icon {
line-height: 25px;
width: 30px;
font-weight: bold;
}
.opts-icon:hover,
.case-delete-icon:hover {
color: #2395f1;
}
.menu-title {
display: flex;
justify-content: space-between;
overflow: hidden;
.casename {
overflow: hidden;
}
.case-delete-icon {
margin-left: 5px;
display: none;
}
.btns {
display: none;
}
}
.menu-title:hover {
.case-delete-icon {
display: block;
}
.btns {
display: block;
}
}
}
.container {
display: block;
width: 100%;
padding: 10px;
}
/*
* note that styling gu-mirror directly is a bad practice because it's too generic.
* you're better off giving the draggable elements a unique class and styling that directly!
*/
.gu-mirror {
padding: 10px;
background-color: rgba(0, 0, 0, 0.2);
transition: opacity 0.4s ease-in-out;
}
.container div {
cursor: move;
cursor: grab;
cursor: -moz-grab;
cursor: -webkit-grab;
margin-bottom: 10px;
}
.container div:last-child {
margin-bottom: 0;
}
.gu-mirror {
cursor: grabbing;
cursor: -moz-grabbing;
cursor: -webkit-grabbing;
}
.container .ex-moved {
background-color: #e74c3c;
}
.container.ex-over {
background-color: rgba(255, 255, 255, 0.3);
}
.handle {
padding: 0 5px;
margin-right: 5px;
background-color: rgba(0, 0, 0, 0.4);
cursor: move;
}
.report {
min-height: 500px;
.case-report-pane {
margin-top: 10px;
}
.url {
overflow: auto;
}
.case-report {
margin: 10px;
.case-report-title {
font-size: 14px;
font-weight: bold;
text-align: right;
padding-right: 20px;
}
pre {
white-space: pre-wrap;
}
}
}
.interface-col {
padding: 24px;
.interface-col-table-header {
background-color: rgb(238, 238, 238);
height: 50px;
line-height: 50px;
text-align: left;
}
.interface-col-table-body {
height: 50px;
line-height: 50px;
}
.interface-col-table-header th {
padding-left: 5px;
}
.interface-col-table-body td {
padding-left: 5px;
}
.interface-col-table-action button {
margin-right: 5px;
padding: 5px 10px;
max-width: 90px;
}
.component-label-wrapper {
margin-top: -10px;
margin-bottom: 15px;
}
}
.import-case-modal {
.ant-modal-body {
max-height: 800px;
overflow-y: scroll;
}
.select-project {
margin-bottom: 16px;
}
}
.autoTestsModal {
.autoTestUrl {
overflow: auto;
background-color: #f5f5f5;
border: 1px solid #f1f1f1ce;
padding: 16px;
}
.autoTestMsg {
padding: 8px 0 16px;
font-size: 12px;
}
.copy-btn {
margin-left: 16px;
height: 50px;
font-size: 14px;
width: 70px;
}
.ant-modal-body {
padding-top: 32px;
}
.row {
margin-bottom: 0.24rem;
}
.label {
text-align: left;
}
}
.tree-wrapper {
min-height: 500px;
overflow-y: scroll;
}