feat(接口定义): JSON 模版初版

This commit is contained in:
fit2-zhao 2020-12-29 14:37:22 +08:00
parent 16163203d9
commit 68e58a9b1b
5 changed files with 367 additions and 253 deletions

View File

@ -1,5 +1,5 @@
<template>
<div>
<div style="min-width: 1200px;margin-bottom: 20px">
<span class="kv-description" v-if="description">
{{ description }}
</span>

View File

@ -90,193 +90,194 @@
</template>
<script>
import {calculate, Scenario} from "../model/ApiTestModel";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
import {calculate, Scenario} from "../model/ApiTestModel";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
export default {
name: "MsApiVariableAdvance",
props: {
parameters: Array,
environment: Object,
scenario: Scenario,
currentItem: Object,
},
data() {
return {
itemValueVisible: false,
filterText: '',
environmentParams: [],
scenarioParams: [],
preRequests: [],
preRequestParams: [],
treeProps: {children: 'children', label: 'name'},
currentTab: 0,
itemValue: null,
itemValuePreview: null,
funcs: [
{name: "md5"},
{name: "base64"},
{name: "unbase64"},
{
name: "substr",
params: [{name: "start"}, {name: "length"}]
},
{
name: "concat",
params: [{name: "suffix"}]
},
{name: "lconcat", params: [{name: "prefix"}]},
{name: "sha1"},
{name: "sha224"},
{name: "sha256"},
{name: "sha384"},
{name: "sha512"},
{name: "lower"},
{name: "upper"},
{name: "length"},
{name: "number"}
],
mockFuncs: MOCKJS_FUNC.map(f => {
return {name: f.name, value: f.name}
}),
jmeterFuncs: JMETER_FUNC,
mockVariableFuncs: [],
jmeterVariableFuncs: [],
}
},
computed: {
valueText() {
return this.valuePlaceholder || this.$t("api_test.value");
}
},
mounted() {
this.prepareData();
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
export default {
name: "MsApiVariableAdvance",
props: {
parameters: Array,
environment: Object,
scenario: Scenario,
currentItem: Object,
},
},
methods: {
open() {
this.itemValueVisible = true;
},
prepareData() {
if (this.scenario) {
let variables = this.scenario.variables;
this.scenarioParams = [
data() {
return {
itemValueVisible: false,
filterText: '',
environmentParams: [],
scenarioParams: [],
preRequests: [],
preRequestParams: [],
treeProps: {children: 'children', label: 'name'},
currentTab: 0,
itemValue: null,
itemValuePreview: null,
funcs: [
{name: "md5"},
{name: "base64"},
{name: "unbase64"},
{
name: this.scenario.name,
children: variables.filter(v => v.name).map(v => {
return {name: v.name, value: '${' + v.name + '}'}
}),
}
];
if (this.environment) {
let variables = this.environment.config.commonConfig.variables;
this.environmentParams = [
name: "substr",
params: [{name: "start"}, {name: "length"}]
},
{
name: "concat",
params: [{name: "suffix"}]
},
{name: "lconcat", params: [{name: "prefix"}]},
{name: "sha1"},
{name: "sha224"},
{name: "sha256"},
{name: "sha384"},
{name: "sha512"},
{name: "lower"},
{name: "upper"},
{name: "length"},
{name: "number"}
],
mockFuncs: MOCKJS_FUNC.map(f => {
return {name: f.name, value: f.name}
}),
jmeterFuncs: JMETER_FUNC,
mockVariableFuncs: [],
jmeterVariableFuncs: [],
}
},
computed: {
valueText() {
return this.valuePlaceholder || this.$t("api_test.value");
}
},
mounted() {
this.prepareData();
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
},
},
methods: {
open() {
this.itemValueVisible = true;
},
prepareData() {
if (this.scenario) {
let variables = this.scenario.variables;
this.scenarioParams = [
{
name: this.environment.name,
name: this.scenario.name,
children: variables.filter(v => v.name).map(v => {
return {name: v.name, value: '${' + v.name + '}'}
}),
}
];
if (this.environment) {
let variables = this.environment.config.commonConfig.variables;
this.environmentParams = [
{
name: this.environment.name,
children: variables.filter(v => v.name).map(v => {
return {name: v.name, value: '${' + v.name + '}'}
}),
}
];
}
let i = this.scenario.requests.indexOf(this.request);
this.preRequests = this.scenario.requests.slice(0, i);
this.preRequests.forEach(r => {
let js = r.extract.json.map(v => {
return {name: v.variable, value: v.value}
});
let xs = r.extract.xpath.map(v => {
return {name: v.variable, value: v.value}
});
let rx = r.extract.regex.map(v => {
return {name: v.variable, value: v.value}
});
let vs = [...js, ...xs, ...rx];
if (vs.length > 0) {
this.preRequestParams.push({name: r.name, children: vs});
}
});
}
let i = this.scenario.requests.indexOf(this.request);
this.preRequests = this.scenario.requests.slice(0, i);
this.preRequests.forEach(r => {
let js = r.extract.json.map(v => {
return {name: v.variable, value: v.value}
});
let xs = r.extract.xpath.map(v => {
return {name: v.variable, value: v.value}
});
let rx = r.extract.regex.map(v => {
return {name: v.variable, value: v.value}
});
let vs = [...js, ...xs, ...rx];
if (vs.length > 0) {
this.preRequestParams.push({name: r.name, children: vs});
},
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
selectVariable(node) {
this.itemValue = node.value;
},
selectTab(tab) {
this.currentTab = +tab.index;
this.itemValue = null;
this.itemValuePreview = null;
},
showPreview() {
//
if (!this.itemValue) {
return;
}
let index = this.itemValue.indexOf("|");
if (index > -1) {
this.itemValue = this.itemValue.substring(0, index).trim();
}
this.mockVariableFuncs.forEach(f => {
if (!f.name) {
return;
}
this.itemValue += "|" + f.name;
if (f.params) {
this.itemValue += ":" + f.params.map(p => p.value).join(",");
}
});
}
},
filterNode(value, data) {
if (!value) return true;
return data.name.indexOf(value) !== -1;
},
selectVariable(node) {
this.itemValue = node.value;
},
selectTab(tab) {
this.currentTab = +tab.index;
this.itemValue = null;
this.itemValuePreview = null;
},
showPreview() {
//
if (!this.itemValue) {
return;
}
let index = this.itemValue.indexOf("|");
if (index > -1) {
this.itemValue = this.itemValue.substring(0, index).trim();
}
this.mockVariableFuncs.forEach(f => {
if (!f.name) {
this.itemValuePreview = calculate(this.itemValue);
},
methodChange(itemFunc, func) {
let index = this.mockVariableFuncs.indexOf(itemFunc);
this.mockVariableFuncs = this.mockVariableFuncs.slice(0, index);
// deep copy
this.mockVariableFuncs.push(JSON.parse(JSON.stringify(func)));
this.showPreview();
},
addFunc() {
if (this.mockVariableFuncs.length > 4) {
this.$info(this.$t('api_test.request.parameters_advance_add_func_limit'));
return;
}
this.itemValue += "|" + f.name;
if (f.params) {
this.itemValue += ":" + f.params.map(p => p.value).join(",");
}
});
this.itemValuePreview = calculate(this.itemValue);
},
methodChange(itemFunc, func) {
let index = this.mockVariableFuncs.indexOf(itemFunc);
this.mockVariableFuncs = this.mockVariableFuncs.slice(0, index);
// deep copy
this.mockVariableFuncs.push(JSON.parse(JSON.stringify(func)));
this.showPreview();
},
addFunc() {
if (this.mockVariableFuncs.length > 4) {
this.$info(this.$t('api_test.request.parameters_advance_add_func_limit'));
return;
}
if (this.mockVariableFuncs.length > 0) {
let func = this.mockVariableFuncs[this.mockVariableFuncs.length - 1];
if (!func.name) {
this.$warning(this.$t('api_test.request.parameters_advance_add_func_error'));
return;
}
if (func.params) {
for (let j = 0; j < func.params.length; j++) {
if (!func.params[j].value) {
this.$warning(this.$t('api_test.request.parameters_advance_add_param_error'));
return;
if (this.mockVariableFuncs.length > 0) {
let func = this.mockVariableFuncs[this.mockVariableFuncs.length - 1];
if (!func.name) {
this.$warning(this.$t('api_test.request.parameters_advance_add_func_error'));
return;
}
if (func.params) {
for (let j = 0; j < func.params.length; j++) {
if (!func.params[j].value) {
this.$warning(this.$t('api_test.request.parameters_advance_add_param_error'));
return;
}
}
}
}
this.mockVariableFuncs.push({name: '', params: []});
},
saveAdvanced() {
this.currentItem.value = this.itemValue;
this.itemValueVisible = false;
this.mockVariableFuncs = [];
this.$emit('advancedRefresh', this.itemValue);
}
this.mockVariableFuncs.push({name: '', params: []});
},
saveAdvanced() {
this.currentItem.value = this.itemValue;
this.itemValueVisible = false;
this.mockVariableFuncs = [];
}
}
}
</script>
<style scoped>
.col-height {
height: 40vh;
overflow: auto;
}
.col-height {
height: 40vh;
overflow: auto;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div style="min-width: 1200px;margin-bottom: 20px">
<div>
<el-radio-group v-model="body.type" size="mini">
<el-radio :disabled="isReadOnly" :label="type.FORM_DATA" @change="modeChange">
{{ $t('api_test.definition.request.body_form_data') }}
@ -40,7 +40,7 @@
type="body"
v-if="body.type == 'WWW_FORM'"/>
<div class="ms-body" v-if="body.type == 'JSON'">
<div v-if="body.type == 'JSON'">
<ms-json-code-edit @json-change="jsonChange" @onError="jsonError" :value="body.raw" ref="jsonCodeEdit"/>
</div>
@ -67,7 +67,7 @@
import MsApiKeyValue from "../ApiKeyValue";
import {BODY_TYPE, KeyValue} from "../../model/ApiTestModel";
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit";
import MsJsonCodeEdit from "../../../../common/json/JsonTable";
import MsDropdown from "../../../../common/components/MsDropdown";
import MsApiVariable from "../ApiVariable";
import MsApiBinaryVariable from "./ApiBinaryVariable";

View File

@ -1,5 +1,5 @@
<template>
<div>
<div style="min-width: 1200px;margin-bottom: 20px">
<span class="kv-description" v-if="description">
{{ description }}
</span>

View File

@ -1,91 +1,119 @@
<template>
<div v-loading="loading">
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
row-key="id"
default-expand-all
@cell-click="editor"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column
prop="name"
label="名称">
<template slot-scope="scope">
<el-input v-if="scope.row.id === tabClickIndex && tabClickProperty === 'name'" v-model="scope.row.name" size="mini" style="padding-left: 20px" @blur="inputBlur(scope.row)"></el-input>
<span v-else>{{scope.row.name}}</span>
</template>
</el-table-column>
<el-table-column
prop="required"
label="必输项"
align="center"
width="80">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.required"/>
</template>
</el-table-column>
<el-table-column
prop="type"
width="120px"
label="类型">
<template slot-scope="scope">
<el-select v-model="scope.row.type" slot="prepend" size="small">
<el-option v-for="item in typeData" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</template>
</el-table-column>
<el-table-column
prop="value"
label="内容">
<template slot-scope="scope">
<el-input v-if="scope.row.id === tabClickIndex && tabClickProperty === 'value'" v-model="scope.row.value" size="mini"></el-input>
<span v-else>{{scope.row.value}}</span>
</template>
</el-table-column>
<el-tabs v-model="activeName" @tab-click="tabChange">
<el-tab-pane label="模版" name="apiTemplate">
<el-button type="primary" size="mini">导入JSON</el-button>
<el-table
:data="tableData"
style="width: 100%;margin-bottom: 20px;"
class="ms-el-header"
row-key="id"
default-expand-all
@cell-click="editor"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}">
<el-table-column
prop="name"
label="">
<template slot-scope="scope">
<el-input v-if="scope.row.id === tabClickIndex && tabClickProperty === 'name'" v-model="scope.row.name" size="mini" style="padding-left: 20px" @blur="inputBlur(scope.row)"></el-input>
<span v-else>{{scope.row.name}}</span>
</template>
</el-table-column>
<el-table-column
prop="required"
label=""
align="center"
width="80">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.required"/>
</template>
</el-table-column>
<el-table-column
prop="type"
width="120px"
label="">
<template slot-scope="scope">
<el-select v-model="scope.row.type" slot="prepend" size="small" @change="typeChange(scope.row)">
<el-option v-for="item in typeData" :key="item.id" :label="item.label" :value="item.id"/>
</el-select>
</template>
</el-table-column>
<el-table-column
prop="value"
label="">
<template slot-scope="scope">
<el-autocomplete
size="small"
:disabled="false"
class="input-with-autocomplete"
v-model="scope.row.value"
:fetch-suggestions="funcSearch"
:placeholder="$t('api_test.value')"
value-key="name"
highlight-first-item
@select="change">
<i slot="suffix" class="el-input__icon el-icon-edit pointer" @click="advanced(scope.row)"></i>
</el-autocomplete>
</template>
</el-table-column>
<el-table-column
prop="describe"
label="描述">
<template slot-scope="scope">
<el-input v-if="scope.row.id === tabClickIndex && tabClickProperty === 'describe'" v-model="scope.row.describe" size="mini"></el-input>
<span v-else>{{scope.row.describe}}</span>
</template>
</el-table-column>
<el-table-column
prop="describe"
label="">
<template slot-scope="scope">
<el-input v-if="scope.row.id === tabClickIndex && tabClickProperty === 'describe'" v-model="scope.row.describe" size="mini"></el-input>
<span v-else>{{scope.row.describe}}</span>
</template>
</el-table-column>
<el-table-column
prop="opt"
label="操作"
width="100">
<template slot-scope="scope">
<div>
<i class="el-icon-setting" style="margin-left: 5px;cursor: pointer" @click="setting(scope.row)"/>
<i class="el-icon-plus" style="margin-left: 5px;cursor: pointer" @click="add(scope.row)"/>
<i class="el-icon-close" style="margin-left: 5px;cursor: pointer" @click="deleteRow(scope.row)"/>
</div>
</template>
</el-table-column>
<el-table-column
prop="opt"
label=""
width="100">
<template slot-scope="scope">
<div>
<i class="el-icon-setting" style="margin-left: 5px;cursor: pointer" @click="setting(scope.row)"/>
<el-tooltip v-if="scope.row.type==='object'" class="item-tabs" effect="dark" content="添加子节点" placement="top-start" slot="label">
<i class="el-icon-plus" style="margin-left: 5px;cursor: pointer" @click="add(scope.row)"/>
</el-tooltip>
<el-tooltip v-else class="item-tabs" effect="dark" content="添加兄弟节点" placement="top-start" slot="label">
<i class="el-icon-plus" style="margin-left: 5px;cursor: pointer" @click="add(scope.row)"/>
</el-tooltip>
<i class="el-icon-close" style="margin-left: 5px;cursor: pointer" v-if="scope.row.id!= 'root'" @click="deleteRow(scope.row)"/>
</div>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="预览" name="preview">
</el-tab-pane>
</el-tabs>
</el-table>
<ms-api-variable-advance ref="variableAdvance" :current-item="currentItem" @advancedRefresh="advancedRefresh"/>
</div>
</template>
<script>
import {getUUID} from "@/common/js/utils";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
import MsApiVariableAdvance from "../../api/definition/components/ApiVariableAdvance";
export default {
name: "MsJsonTable",
components: {},
components: {MsApiVariableAdvance},
data() {
return {
loading: false,
activeName: "apiTemplate",
parameters: [],
currentItem: null,
tableData: [{
id: "root",
parent: null,
name: 'root',
required: true,
type: 'object',
value: 'object',
value: 'value',
describe: 'describe',
editor: false,
children: [],
@ -105,12 +133,17 @@
},
methods: {
editor(row, column) {
if (row.id === 'root') {
this.tabClickIndex = null;
this.tabClickProperty = '';
return;
}
switch (column.property) {
case 'name':
this.tabClickIndex = row.id
this.tabClickProperty = column.property
break
case 'value':
case '':
this.tabClickIndex = row.id
this.tabClickProperty = column.property
break;
@ -127,6 +160,28 @@
this.tabClickProperty = '';
this.reload();
},
jsonItem() {
let obj = {
id: getUUID(),
name: 'field',
required: true,
type: 'string',
value: '',
parent: null,
describe: '',
children: [],
}
return obj;
},
typeChange(row) {
if (row.type === "array") {
let obj = this.jsonItem();
obj.name = obj.name + "_" + row.children.length;
row.children.push(obj);
} else {
row.children = [];
}
},
reload() {
this.loading = true;
this.$nextTick(() => {
@ -137,21 +192,10 @@
},
add(row) {
let obj = {
id: getUUID(),
name: 'field',
required: true,
type: 'string',
value: 'test',
parent: null,
describe: 'describe',
children: [],
}
console.log("all", this.tableData)
console.log("row", row)
let obj = this.jsonItem();
if (row.type === "object") {
obj.parent = row.id;
obj.name = obj.name + "_" + row.children.length;
row.children.push(obj);
return;
}
@ -162,24 +206,36 @@
} else {
for (let i in this.tableData) {
if (this.tableData[i].children) {
parentRow = this.recursiveRemove(this.tableData[i].children, row);
parentRow = this.recursiveFind(this.tableData[i].children, row);
}
}
}
console.log(parentRow)
if (parentRow) {
obj.parent = parentRow.id;
obj.name = obj.name + "_" + parentRow.children.length;
parentRow.children.push(obj);
return;
}
obj.name = obj.name + "_" + this.tableData.length;
this.tableData.push(obj);
},
recursiveFind(arr, row) {
for (let i in arr) {
const index = arr.findIndex(d => d.id != undefined && row.parent != undefined && d.id === row.parent)
if (index != -1) {
return arr[i];
}
if (arr[i].children != undefined && arr[i].children.length > 0) {
this.recursiveFind(arr[i].children, row);
}
}
},
recursiveRemove(arr, row) {
for (let i in arr) {
const index = arr.findIndex(d => d.id != undefined && row.id != undefined && d.id === row.id)
if (index != -1) {
arr.splice(index, 1);
return arr[i];
}
if (arr[i].children != undefined && arr[i].children.length > 0) {
this.recursiveRemove(arr[i].children, row);
@ -197,6 +253,41 @@
} else {
this.tableData.splice(index, 1);
}
},
tabChange() {
},
funcSearch(queryString, cb) {
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
// callback
cb(results);
},
funcFilter(queryString) {
return (func) => {
return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
};
},
change: function () {
let isNeedCreate = true;
let removeIndex = -1;
this.parameters.forEach((item, index) => {
if (!item.name && !item.value) {
//
if (index !== this.parameters.length - 1) {
removeIndex = index;
}
//
isNeedCreate = false;
}
});
},
advanced(item) {
this.$refs.variableAdvance.open();
this.currentItem = item;
},
advancedRefresh(item) {
}
}
}
@ -208,4 +299,26 @@
height: 25px;
line-height: 25px;
}
.ms-el-header >>> .el-table {
padding: 0px 0;
}
/deep/ th {
/* 去除头部*/
padding: 0px 0;
}
/deep/ td {
padding: 6px 0;
}
.advanced-item-value >>> .el-dialog__body {
padding: 15px 25px;
}
.pointer {
cursor: pointer;
color: #1E90FF;
}
</style>