api界面,未完待续
This commit is contained in:
parent
336b6c4af6
commit
f9f1809f3e
|
@ -0,0 +1,170 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="main-content">
|
||||
<el-card>
|
||||
<el-container class="scenario-container">
|
||||
<el-header>
|
||||
<span class="scenario-title">场景配置</span>
|
||||
</el-header>
|
||||
|
||||
<el-container>
|
||||
<el-aside class="scenario-aside">
|
||||
<div class="scenario-list">
|
||||
<ms-api-collapse v-model="activeName" @change="handleChange" accordion>
|
||||
<ms-api-collapse-item v-for="(scenario, index) in scenarios" :key="index"
|
||||
:title="scenario.name" :name="index">
|
||||
<template slot="title">
|
||||
<div class="scenario-name">{{scenario.name}}</div>
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link el-icon-more scenario-btn"/>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="{type:'delete', index:index}">删除场景</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<ms-api-request :requests="scenario.requests" :open="select"/>
|
||||
</ms-api-collapse-item>
|
||||
</ms-api-collapse>
|
||||
</div>
|
||||
<el-button class="scenario-create" type="primary" size="mini" icon="el-icon-plus" plain @click="create"/>
|
||||
</el-aside>
|
||||
|
||||
<el-main class="scenario-main">
|
||||
<div class="scenario-form">
|
||||
<ms-api-scenario-form :scenario="selected"></ms-api-scenario-form>
|
||||
<ms-api-request-form :request="selected"></ms-api-request-form>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import MsApiCollapseItem from "./components/ApiCollapseItem";
|
||||
import MsApiCollapse from "./components/ApiCollapse";
|
||||
import MsApiRequest from "./components/ApiRequest";
|
||||
import MsApiRequestForm from "./components/ApiRequestForm";
|
||||
import MsApiScenarioForm from "./components/ApiScenarioForm";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioConfig",
|
||||
|
||||
components: {MsApiScenarioForm, MsApiRequestForm, MsApiRequest, MsApiCollapse, MsApiCollapseItem},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeName: 0,
|
||||
scenarios: [],
|
||||
selected: Object
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleChange: function (index) {
|
||||
this.select(this.scenarios[index]);
|
||||
},
|
||||
handleCommand: function (command) {
|
||||
switch (command.type) {
|
||||
case "delete":
|
||||
this.deleteScenario(command.index);
|
||||
break;
|
||||
}
|
||||
},
|
||||
createScenario: function () {
|
||||
return {
|
||||
type: "Scenario",
|
||||
name: "Scenario",
|
||||
address: "",
|
||||
file: "",
|
||||
variables: [],
|
||||
headers: [],
|
||||
requests: []
|
||||
}
|
||||
},
|
||||
deleteScenario: function (index) {
|
||||
this.scenarios.splice(index, 1);
|
||||
if (this.scenarios.length === 0) {
|
||||
this.create();
|
||||
}
|
||||
},
|
||||
create: function () {
|
||||
let scenario = this.createScenario();
|
||||
this.scenarios.push(scenario);
|
||||
},
|
||||
select: function (obj) {
|
||||
this.selected = obj;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.scenarios.length === 0) {
|
||||
this.create();
|
||||
this.select(this.scenarios[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scenario-container {
|
||||
height: calc(100vh - 150px);
|
||||
min-height: 600px;
|
||||
}
|
||||
|
||||
.scenario-title {
|
||||
font-size: 16px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
.scenario-aside {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #EBEEF5;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scenario-list {
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 28px;
|
||||
}
|
||||
|
||||
.scenario-name {
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scenario-btn {
|
||||
text-align: center;
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.scenario-create {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.scenario-main {
|
||||
position: relative;
|
||||
margin-left: 20px;
|
||||
border: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.scenario-form {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,242 +0,0 @@
|
|||
<template>
|
||||
<div class="edit-testplan-container" >
|
||||
<div class="main-content">
|
||||
<el-card>
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
<el-input :placeholder="$t('load_test.input_name')" v-model="testPlan.name" class="input-with-select">
|
||||
<template v-slot:prepend>
|
||||
<el-select v-model="testPlan.projectId" :placeholder="$t('load_test.select_project')">
|
||||
<el-option
|
||||
v-for="item in projects"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-button type="primary" plain @click="save">{{$t('commons.save')}}</el-button>
|
||||
<el-button type="primary" plain @click="saveAndRun">{{$t('load_test.save_and_run')}}</el-button>
|
||||
<el-button type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button>
|
||||
</el-row>
|
||||
|
||||
<el-tabs class="testplan-config" v-model="active" type="border-card" :stretch="true">
|
||||
<el-tab-pane :label="$t('load_test.basic_config')">
|
||||
<api-test-scene-config :test-plan="testPlan" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('load_test.runtime_config')">
|
||||
<api-test-runtime-config :test-plan="testPlan" ref="runtimeConfig"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ApiTestSceneConfig from './components/ApiTestSceneConfig';
|
||||
import ApiTestRuntimeConfig from './components/ApiTestRuntimeConfig';
|
||||
|
||||
export default {
|
||||
name: "EditApiTest",
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
testPlan: {},
|
||||
listProjectPath: "/project/listAll",
|
||||
savePath: "/api/save",
|
||||
editPath: "/api/edit",
|
||||
runPath: "/api/run",
|
||||
projects: [],
|
||||
active: '0',
|
||||
tabs: [{
|
||||
title: this.$t('load_test.basic_config'),
|
||||
id: '0',
|
||||
component: 'ApiTestSceneConfig'
|
||||
}, {
|
||||
title: this.$t('load_test.runtime_config'),
|
||||
id: '1',
|
||||
component: 'ApiTestRuntimeConfig'
|
||||
}]
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ApiTestSceneConfig,
|
||||
ApiTestRuntimeConfig,
|
||||
},
|
||||
watch: {
|
||||
'$route'(to) {
|
||||
// 如果是创建测试
|
||||
if (to.name === 'createFucTest') {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
let testId = to.path.split('/')[4]; // find testId
|
||||
if (testId) {
|
||||
this.$get('/api/get/' + testId, response => {
|
||||
this.testPlan = response.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
created() {
|
||||
let testId = this.$route.path.split('/')[4];
|
||||
if (testId) {
|
||||
this.$get('/api/get/' + testId, response => {
|
||||
this.testPlan = response.data;
|
||||
});
|
||||
}
|
||||
|
||||
this.listProjects();
|
||||
},
|
||||
methods: {
|
||||
listProjects() {
|
||||
this.result = this.$get(this.listProjectPath, response => {
|
||||
this.projects = response.data;
|
||||
})
|
||||
},
|
||||
save() {
|
||||
if (!this.validTestPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = this.getSaveOption();
|
||||
|
||||
this.result = this.$request(options, () => {
|
||||
this.$message({
|
||||
message: this.$t('commons.save_success'),
|
||||
type: 'success'
|
||||
});
|
||||
this.$refs.runtimeConfig.cancelAllEdit();
|
||||
this.$router.push({path: '/api/test/all'})
|
||||
});
|
||||
},
|
||||
saveAndRun() {
|
||||
if (!this.validTestPlan()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let options = this.getSaveOption();
|
||||
|
||||
this.result = this.$request(options, (response) => {
|
||||
this.testPlan.id = response.data;
|
||||
this.$message({
|
||||
message: this.$t('commons.save_success'),
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
this.result = this.$post(this.runPath, {id: this.testPlan.id}, () => {
|
||||
this.$message({
|
||||
message: this.$t('load_test.is_running'),
|
||||
type: 'success'
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
getSaveOption() {
|
||||
let formData = new FormData();
|
||||
let url = this.testPlan.id ? this.editPath : this.savePath;
|
||||
|
||||
if (!this.testPlan.file.id) {
|
||||
formData.append("file", this.testPlan.file);
|
||||
}
|
||||
|
||||
this.testPlan.runtimeConfiguration = JSON.stringify(this.$refs.runtimeConfig.configurations());
|
||||
|
||||
// file属性不需要json化
|
||||
let requestJson = JSON.stringify(this.testPlan, function (key, value) {
|
||||
return key === "file" ? undefined : value
|
||||
});
|
||||
|
||||
formData.append('request', new Blob([requestJson], {
|
||||
type: "application/json"
|
||||
}));
|
||||
|
||||
return {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': undefined
|
||||
}
|
||||
};
|
||||
},
|
||||
cancel() {
|
||||
this.$router.push({path: '/api/test/all'})
|
||||
},
|
||||
validTestPlan() {
|
||||
if (!this.testPlan.name) {
|
||||
this.$message({
|
||||
message: this.$t('load_test.test_name_is_null'),
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.testPlan.projectId) {
|
||||
this.$message({
|
||||
message: this.$t('load_test.project_is_null'),
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.testPlan.file) {
|
||||
this.$message({
|
||||
message: this.$t('load_test.jmx_is_null'),
|
||||
type: 'error'
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.$refs.runtimeConfig.validConfig()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// todo: 其他校验
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.edit-testplan-container {
|
||||
float: none;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.edit-testplan-container .main-content {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.edit-testplan-container .testplan-config {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.el-select {
|
||||
min-width: 130px;
|
||||
}
|
||||
|
||||
.edit-testplan-container .input-with-select .el-input-group__prepend {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.advanced-config {
|
||||
height: calc(100vh - 280px);
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-radio-group v-model="body.type" size="mini">
|
||||
<el-radio-button label="kv">
|
||||
{{$t('api_test.request.body_kv')}}
|
||||
</el-radio-button>
|
||||
<el-radio-button label="text">
|
||||
{{$t('api_test.request.body_text')}}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<ms-api-key-value :items="body.kvs" v-if="isKV"/>
|
||||
|
||||
<el-input class="textarea" type="textarea" v-model="body.text" :autosize="{ minRows: 10, maxRows: 25}" resize="none"
|
||||
v-else/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "./ApiKeyValue";
|
||||
|
||||
export default {
|
||||
name: "MsApiBody",
|
||||
components: {MsApiKeyValue},
|
||||
props: {
|
||||
body: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
|
||||
methods: {},
|
||||
|
||||
computed: {
|
||||
isKV() {
|
||||
return this.body.type === "kv";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.textarea {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div role="tablist" aria-multiselectable="true">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'MsApiCollapse',
|
||||
|
||||
componentName: 'MsApiCollapse',
|
||||
|
||||
props: {
|
||||
accordion: Boolean,
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeNames: [].concat(this.value)
|
||||
};
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
collapse: this
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(value) {
|
||||
this.activeNames = [].concat(value);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
setActiveNames(activeNames) {
|
||||
activeNames = [].concat(activeNames);
|
||||
let value = this.accordion ? activeNames[0] : activeNames;
|
||||
this.activeNames = activeNames;
|
||||
this.$emit('input', value);
|
||||
this.$emit('change', value);
|
||||
},
|
||||
handleItemClick(item) {
|
||||
if (this.accordion) {
|
||||
this.setActiveNames(
|
||||
(this.activeNames[0] || this.activeNames[0] === 0) && item.name);
|
||||
} else {
|
||||
let activeNames = this.activeNames.slice(0);
|
||||
let index = activeNames.indexOf(item.name);
|
||||
|
||||
if (index > -1) {
|
||||
activeNames.splice(index, 1);
|
||||
} else {
|
||||
activeNames.push(item.name);
|
||||
}
|
||||
this.setActiveNames(activeNames);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$on('item-click', this.handleItemClick);
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div class="el-collapse-item"
|
||||
:class="{'is-active': isActive, 'is-disabled': disabled }">
|
||||
<div
|
||||
role="tab"
|
||||
:aria-expanded="isActive"
|
||||
:aria-controls="`el-collapse-content-${id}`"
|
||||
:aria-describedby="`el-collapse-content-${id}`"
|
||||
>
|
||||
<div
|
||||
class="el-collapse-item__header"
|
||||
@click="handleHeaderClick"
|
||||
role="button"
|
||||
:id="`el-collapse-head-${id}`"
|
||||
:tabindex="disabled ? undefined : 0"
|
||||
@keyup.space.enter.stop="handleEnterClick"
|
||||
:class="{
|
||||
'focusing': focusing,
|
||||
'is-active': isActive
|
||||
}"
|
||||
@focus="handleFocus"
|
||||
@blur="focusing = false"
|
||||
>
|
||||
<i
|
||||
class="el-collapse-item__arrow el-icon-arrow-right"
|
||||
:class="{'is-active': isActive}">
|
||||
</i>
|
||||
<slot name="title">{{title}}</slot>
|
||||
</div>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div
|
||||
class="el-collapse-item__wrap"
|
||||
v-show="isActive"
|
||||
role="tabpanel"
|
||||
:aria-hidden="!isActive"
|
||||
:aria-labelledby="`el-collapse-head-${id}`"
|
||||
:id="`el-collapse-content-${id}`"
|
||||
>
|
||||
<div class="el-collapse-item__content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Emitter from 'element-ui/src/mixins/emitter';
|
||||
import {generateId} from 'element-ui/src/utils/util';
|
||||
|
||||
export default {
|
||||
name: 'MsApiCollapseItem',
|
||||
|
||||
componentName: 'MsApiCollapseItem',
|
||||
|
||||
mixins: [Emitter],
|
||||
|
||||
data() {
|
||||
return {
|
||||
contentWrapStyle: {
|
||||
height: 'auto',
|
||||
display: 'block'
|
||||
},
|
||||
contentHeight: 0,
|
||||
focusing: false,
|
||||
isClick: false,
|
||||
id: generateId()
|
||||
};
|
||||
},
|
||||
|
||||
inject: ['collapse'],
|
||||
|
||||
props: {
|
||||
title: String,
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default() {
|
||||
return this._uid;
|
||||
}
|
||||
},
|
||||
disabled: Boolean
|
||||
},
|
||||
|
||||
computed: {
|
||||
isActive() {
|
||||
return this.collapse.activeNames.indexOf(this.name) > -1;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleFocus() {
|
||||
setTimeout(() => {
|
||||
if (!this.isClick) {
|
||||
this.focusing = true;
|
||||
} else {
|
||||
this.isClick = false;
|
||||
}
|
||||
}, 50);
|
||||
},
|
||||
handleHeaderClick() {
|
||||
if (this.disabled) return;
|
||||
this.dispatch('MsApiCollapse', 'item-click', this);
|
||||
this.focusing = false;
|
||||
this.isClick = true;
|
||||
},
|
||||
handleEnterClick() {
|
||||
this.dispatch('MsApiCollapse', 'item-click', this);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-collapse-item__header {
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
.el-collapse-item__header.is-active {
|
||||
background-color: #E9E9E9;
|
||||
}
|
||||
|
||||
.el-collapse-item__content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div>
|
||||
<span class="kv-description" v-if="description">
|
||||
{{description}}
|
||||
</span>
|
||||
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col :span="11">
|
||||
<el-input v-model="item.key" placeholder="Key" size="small" maxlength="100" @change="check"/>
|
||||
</el-col>
|
||||
<el-col :span="11">
|
||||
<el-input v-model="item.value" placeholder="Value" size="small" maxlength="100" @change="check"/>
|
||||
</el-col>
|
||||
<el-col :span="1">
|
||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsApiKeyValue",
|
||||
|
||||
props: {
|
||||
description: String,
|
||||
items: Array
|
||||
},
|
||||
|
||||
methods: {
|
||||
create: function () {
|
||||
return {
|
||||
key: "",
|
||||
value: ""
|
||||
}
|
||||
},
|
||||
remove: function (index) {
|
||||
this.items.splice(index, 1);
|
||||
if (this.items.length === 0) {
|
||||
this.items.push(this.create());
|
||||
}
|
||||
},
|
||||
check: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.items.forEach((item, index) => {
|
||||
if (item.key === "" && item.value === "") {
|
||||
// 多余的空行
|
||||
if (index !== this.items.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.items.push(this.create());
|
||||
}
|
||||
if (removeIndex !== -1) {
|
||||
this.remove(removeIndex);
|
||||
}
|
||||
// TODO 检查key重复
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.items.length === 0) {
|
||||
this.items.push(this.create());
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,151 @@
|
|||
<template>
|
||||
<div class="request-container">
|
||||
<div class="request-item" v-for="(request, index) in requests" :key="index" @click="select(request)"
|
||||
:class="{'selected': isSelected(request)}">
|
||||
<span class="request-method">
|
||||
{{request.method}}
|
||||
</span>
|
||||
<span class="request-name">
|
||||
{{request.name}}
|
||||
</span>
|
||||
<span class="request-btn">
|
||||
<el-dropdown trigger="click" @command="handleCommand">
|
||||
<span class="el-dropdown-link el-icon-more"></span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item :command="{type: 'copy', index: index}">复制请求</el-dropdown-item>
|
||||
<el-dropdown-item :command="{type: 'delete', index: index}">删除请求</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
<el-button class="request-create" type="primary" size="mini" icon="el-icon-plus" plain @click="create"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {generateId} from 'element-ui/src/utils/util';
|
||||
|
||||
export default {
|
||||
name: "MsApiRequest",
|
||||
props: {
|
||||
requests: Array,
|
||||
open: Function
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
selected: 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isSelected() {
|
||||
return function (request) {
|
||||
return this.selected.randomId === request.randomId;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
create: function () {
|
||||
let request = this.createRequest();
|
||||
this.requests.push(request);
|
||||
},
|
||||
handleCommand: function (command) {
|
||||
switch (command.type) {
|
||||
case "copy":
|
||||
this.copyRequest(command.index);
|
||||
break;
|
||||
case "delete":
|
||||
this.deleteRequest(command.index);
|
||||
break;
|
||||
}
|
||||
},
|
||||
copyRequest: function (index) {
|
||||
let request = this.requests[index];
|
||||
this.requests.push(JSON.parse(JSON.stringify(request)));
|
||||
},
|
||||
deleteRequest: function (index) {
|
||||
this.requests.splice(index, 1);
|
||||
if (this.requests.length === 0) {
|
||||
this.create();
|
||||
}
|
||||
},
|
||||
createRequest: function () {
|
||||
return {
|
||||
randomId: generateId(),
|
||||
type: "Request",
|
||||
method: "GET",
|
||||
name: "",
|
||||
parameters: [],
|
||||
headers: [],
|
||||
body: {
|
||||
type: "kv",
|
||||
kvs: [],
|
||||
text: ""
|
||||
},
|
||||
assertions: [],
|
||||
extract: []
|
||||
}
|
||||
},
|
||||
select: function (request) {
|
||||
this.selected = request;
|
||||
this.open(request);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.requests.length === 0) {
|
||||
this.create();
|
||||
this.select(this.requests[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-item {
|
||||
border-left: 5px solid #1E90FF;
|
||||
line-height: 40px;
|
||||
max-height: 40px;
|
||||
border-top: 1px solid #EBEEF5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-item:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.request-item:hover, .request-item.selected:hover {
|
||||
background-color: #ECF5FF;
|
||||
}
|
||||
|
||||
.request-item.selected {
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.request-method {
|
||||
padding: 0 5px;
|
||||
width: 60px;
|
||||
color: #1E90FF;
|
||||
}
|
||||
|
||||
.request-name {
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.request-btn {
|
||||
float: right;
|
||||
text-align: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.request-btn .el-icon-more {
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.request-create {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<el-form :model="request" :rules="rules" ref="request" label-width="100px" label-position="left" v-if="isRequest">
|
||||
<el-form-item :label="$t('api_test.request.name')" prop="name">
|
||||
<el-input v-model="request.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('api_test.request.url')" prop="url">
|
||||
<el-input v-model="request.url" :placeholder="$t('api_test.request.url_describe')">
|
||||
<el-select v-model="request.method" slot="prepend" class="request-method-select">
|
||||
<el-option label="GET" value="GET"></el-option>
|
||||
<el-option label="POST" value="POST"></el-option>
|
||||
<el-option label="PUT" value="PUT"></el-option>
|
||||
<el-option label="PATCH" value="PATCH"></el-option>
|
||||
<el-option label="DELETE" value="DELETE"></el-option>
|
||||
<el-option label="OPTIONS" value="OPTIONS"></el-option>
|
||||
<el-option label="HEAD" value="HEAD"></el-option>
|
||||
<el-option label="CONNECT" value="CONNECT"></el-option>
|
||||
</el-select>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane :label="$t('api_test.request.parameters')" name="parameters">
|
||||
<ms-api-key-value :items="request.parameters" :description="$t('api_test.request.parameters_desc')"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
|
||||
<ms-api-key-value :items="request.headers"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.body')" name="body" v-if="isNotGet">
|
||||
<ms-api-body :body="request.body"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.assertions')" name="assertions" v-if="false">
|
||||
TODO
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.request.extract')" name="extract" v-if="false">
|
||||
TODO
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "./ApiKeyValue";
|
||||
import MsApiBody from "./ApiBody";
|
||||
|
||||
export default {
|
||||
name: "MsApiRequestForm",
|
||||
components: {MsApiBody, MsApiKeyValue},
|
||||
props: {
|
||||
request: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeName: "parameters",
|
||||
rules: {}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isRequest() {
|
||||
return this.request.type === "Request";
|
||||
},
|
||||
isNotGet() {
|
||||
return this.request.method !== "GET";
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-method-select {
|
||||
width: 110px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<el-form :model="scenario" :rules="rules" ref="scenario" label-width="100px" label-position="left" v-if="isScenario">
|
||||
<el-form-item :label="$t('api_test.scenario.name')" prop="name">
|
||||
<el-input v-model="scenario.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('api_test.scenario.base_url')" prop="url">
|
||||
<el-input :placeholder="$t('api_test.scenario.base_url_describe')" v-model="scenario.url"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane :label="$t('api_test.scenario.variables')" name="variables">
|
||||
<ms-api-key-value :items="scenario.variables"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.scenario.headers')" name="headers">
|
||||
<ms-api-key-value :items="scenario.headers"/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "./ApiKeyValue";
|
||||
|
||||
export default {
|
||||
name: "MsApiScenarioForm",
|
||||
components: {MsApiKeyValue},
|
||||
props: {
|
||||
scenario: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
activeName: "variables",
|
||||
rules: {}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isScenario() {
|
||||
return this.scenario.type === "Scenario";
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -1,121 +0,0 @@
|
|||
<template>
|
||||
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="5" :offset="6">
|
||||
<span>浏览器</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row >
|
||||
<el-col :span="20" :offset="2">
|
||||
<el-radio-group v-model="browser.value" class="browser-radio">
|
||||
<el-radio v-for="item in browser.options" :key="item.label" :label="item.label">
|
||||
<img :src="item.url"/>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="5" :offset="6">
|
||||
<span>资源池</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="18" :offset="1">
|
||||
<el-select v-model="resourcePool.value" filterable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in resourcePool.options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "ApiTestRuntimeConfig",
|
||||
data() {
|
||||
return {
|
||||
resourcePool: {
|
||||
options: [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '资源池1'
|
||||
},
|
||||
{
|
||||
value: '选项2',
|
||||
label: '资源池3'
|
||||
},
|
||||
{
|
||||
value: '选项3',
|
||||
label: '资源池3'
|
||||
}],
|
||||
value: ''
|
||||
},
|
||||
browser: {
|
||||
options: [{
|
||||
url: require('@/assets/browser/firefox.svg'),
|
||||
label: 'firefox',
|
||||
},
|
||||
{
|
||||
url: require('@/assets/browser/chrome.svg'),
|
||||
label: 'chrome',
|
||||
},
|
||||
{
|
||||
url: require('@/assets/browser/ie.svg'),
|
||||
label: 'ie',
|
||||
},
|
||||
{
|
||||
url: require('@/assets/browser/opera.svg'),
|
||||
label: 'opera',
|
||||
}
|
||||
],
|
||||
value: 'firefox'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validConfig() {
|
||||
if (this.resourcePool.value == '') {
|
||||
this.$message.error(this.$t('api_test.select_resource_pool'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
configurations() {
|
||||
return {
|
||||
resourcePool: this.resourcePool,
|
||||
browser: this.browser
|
||||
}
|
||||
},
|
||||
cancelAllEdit() {
|
||||
this.browser.value = 'firefox';
|
||||
this.resourcePool.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.el-row {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: dimgray;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -1,196 +0,0 @@
|
|||
<template>
|
||||
<div v-loading="result.loading">
|
||||
<el-upload
|
||||
accept=".jmx"
|
||||
drag
|
||||
action=""
|
||||
:limit="1"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
:http-request="handleUpload"
|
||||
:on-exceed="handleExceed"
|
||||
:file-list="fileList">
|
||||
<i class="el-icon-upload"/>
|
||||
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">{{$t('load_test.upload_type')}}</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<el-table class="basic-config" :data="tableData">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
:label="$t('load_test.file_name')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="size"
|
||||
:label="$t('load_test.file_size')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="type"
|
||||
:label="$t('load_test.file_type')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('load_test.last_modify_time')">
|
||||
<template v-slot:default="scope">
|
||||
<i class="el-icon-time"/>
|
||||
<span class="last-modified">{{ scope.row.lastModified | timestampFormatDate }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
:label="$t('load_test.file_status')">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
:label="$t('commons.operating')">
|
||||
<template v-slot:default="scope">
|
||||
<el-button @click="handleDownload(scope.row)" :disabled="!scope.row.id" type="primary" icon="el-icon-download"
|
||||
size="mini" circle/>
|
||||
<el-button @click="handleDelete(scope.row, scope.$index)" type="danger" icon="el-icon-delete" size="mini"
|
||||
circle/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {Message} from "element-ui";
|
||||
|
||||
export default {
|
||||
name: "ApiTestSceneConfig",
|
||||
props: ["testPlan"],
|
||||
data() {
|
||||
return {
|
||||
result: {},
|
||||
getFileMetadataPath: "/api/file/metadata",
|
||||
jmxDownloadPath: '/api/file/download',
|
||||
jmxDeletePath: '/api/file/delete',
|
||||
fileList: [],
|
||||
tableData: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
testPlan() {
|
||||
if (this.testPlan.id) {
|
||||
this.getFileMetadata(this.testPlan)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFileMetadata(testPlan) {
|
||||
this.fileList = [];// 一个测试只有一个文件
|
||||
this.tableData = [];// 一个测试只有一个文件
|
||||
this.result = this.$get(this.getFileMetadataPath + "/" + testPlan.id, response => {
|
||||
let file = response.data;
|
||||
|
||||
if (!file) {
|
||||
Message.error({message: this.$t('load_test.related_file_not_found'), showClose: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this.testPlan.file = file;
|
||||
this.fileList.push({
|
||||
id: file.id,
|
||||
name: file.name
|
||||
});
|
||||
|
||||
this.tableData.push({
|
||||
id: file.id,
|
||||
name: file.name,
|
||||
size: file.size + 'Byte', /// todo: 按照大小显示Byte、KB、MB等
|
||||
type: 'JMX',
|
||||
lastModified: file.updateTime,
|
||||
status: 'todo',
|
||||
});
|
||||
})
|
||||
},
|
||||
beforeUpload(file) {
|
||||
if (!this.fileValidator(file)) {
|
||||
/// todo: 显示错误信息
|
||||
return false;
|
||||
}
|
||||
|
||||
this.tableData.push({
|
||||
name: file.name,
|
||||
size: file.size + 'Byte', /// todo: 按照大小显示Byte、KB、MB等
|
||||
type: 'JMX',
|
||||
lastModified: file.lastModified,
|
||||
status: 'todo',
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
handleUpload(uploadResources) {
|
||||
this.testPlan.file = uploadResources.file;
|
||||
},
|
||||
handleDownload(file) {
|
||||
let data = {
|
||||
name: file.name,
|
||||
id: file.id,
|
||||
};
|
||||
let config = {
|
||||
url: this.jmxDownloadPath,
|
||||
method: 'post',
|
||||
data: data,
|
||||
responseType: 'blob'
|
||||
};
|
||||
this.result = this.$request(config).then(response => {
|
||||
const content = response.data;
|
||||
const blob = new Blob([content]);
|
||||
if ("download" in document.createElement("a")) {
|
||||
// 非IE下载
|
||||
// chrome/firefox
|
||||
let aTag = document.createElement('a');
|
||||
aTag.download = file.name;
|
||||
aTag.href = URL.createObjectURL(blob);
|
||||
aTag.click();
|
||||
URL.revokeObjectURL(aTag.href)
|
||||
} else {
|
||||
// IE10+下载
|
||||
navigator.msSaveBlob(blob, this.filename)
|
||||
}
|
||||
}).catch(e => {
|
||||
Message.error({message: e.message, showClose: true});
|
||||
});
|
||||
},
|
||||
handleDelete(file, index) {
|
||||
this.$alert(this.$t('commons.delete_file_confirm') + file.name + "?", '', {
|
||||
confirmButtonText: this.$t('commons.confirm'),
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
this._handleDelete(file, index);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
_handleDelete(file, index) {
|
||||
this.fileList.splice(index, 1);
|
||||
this.tableData.splice(index, 1);
|
||||
this.testPlan.file = null;
|
||||
},
|
||||
handleExceed() {
|
||||
this.$message.error(this.$t('load_test.delete_file'));
|
||||
},
|
||||
fileValidator(file) {
|
||||
/// todo: 是否需要对文件内容和大小做限制
|
||||
return file.size > 0;
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.basic-config {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.last-modified {
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
|
@ -18,7 +18,7 @@ import PerformanceTestReport from "../../performance/report/PerformanceTestRepor
|
|||
import ApiTestReport from "../../api/report/ApiTestReport";
|
||||
import ApiTest from "../../api/ApiTest";
|
||||
import PerformanceTest from "../../performance/PerformanceTest";
|
||||
import EditApiTest from "../../api/test/EditApiTest";
|
||||
import ApiScenarioConfig from "../../api/test/ApiScenarioConfig";
|
||||
import PerformanceTestHome from "../../performance/home/PerformanceTestHome";
|
||||
import ApiTestList from "../../api/test/ApiTestList";
|
||||
import ApiTestHome from "../../api/home/ApiTestHome";
|
||||
|
@ -96,13 +96,13 @@ const router = new VueRouter({
|
|||
},
|
||||
{
|
||||
path: 'test/create',
|
||||
name: "createFucTest",
|
||||
component: EditApiTest,
|
||||
name: "createAPITest",
|
||||
component: ApiScenarioConfig,
|
||||
},
|
||||
{
|
||||
path: "test/edit/:testId",
|
||||
name: "editFucTest",
|
||||
component: EditApiTest,
|
||||
name: "editAPITest",
|
||||
component: ApiScenarioConfig,
|
||||
props: {
|
||||
content: (route) => {
|
||||
return {
|
||||
|
|
|
@ -168,7 +168,27 @@ export default {
|
|||
'resource_pool_is_null': '资源池为空',
|
||||
},
|
||||
api_test: {
|
||||
'select_resource_pool': '请选择资源池'
|
||||
scenario: {
|
||||
name: "场景名称",
|
||||
base_url: "基础URL",
|
||||
base_url_describe: "基础URL作为所有请求的URL前缀",
|
||||
variables: "变量",
|
||||
headers: "请求头"
|
||||
},
|
||||
request: {
|
||||
name: "请求名称",
|
||||
method: "请求方法",
|
||||
url: "请求URL",
|
||||
url_describe: "例如: https://fit2cloud.com",
|
||||
parameters: "请求参数",
|
||||
parameters_desc: "参数追加到URL,例如https://fit2cloud.com/entries?key1=Value1&Key2=Value2",
|
||||
headers: "请求头",
|
||||
body: "请求内容",
|
||||
body_kv: "键值对",
|
||||
body_text: "文本",
|
||||
assertions: "断言",
|
||||
extract: "提取"
|
||||
}
|
||||
},
|
||||
test_track: {
|
||||
'test_track': '测试跟踪',
|
||||
|
|
Loading…
Reference in New Issue