fix(接口自动化): 增加json 列表编辑器

This commit is contained in:
fit2-zhao 2020-12-30 11:03:59 +08:00
parent 68e58a9b1b
commit 22fee64092
30 changed files with 2951 additions and 327 deletions

View File

@ -134,4 +134,5 @@ public class ApiDefinitionController {
public void testPlanRelevance(@RequestBody ApiCaseRelevanceRequest request) {
apiDefinitionService.testPlanRelevance(request);
}
}

View File

@ -41,7 +41,11 @@
"vuedraggable": "^2.23.2",
"vuex": "^3.1.2",
"xml-js": "^1.6.11",
"yan-progress": "^1.0.3"
"yan-progress": "^1.0.3",
"lodash": "^4.17.19",
"json-schema-faker": "^0.5.0-rcv.24",
"jsonlint": "^1.6.3",
"codemirror": "^5.58.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
@ -52,7 +56,10 @@
"eslint-plugin-vue": "^5.0.0",
"file-writer": "^1.0.2",
"vue-template-compiler": "^2.6.10",
"vue2-ace-editor": "0.0.15"
"vue2-ace-editor": "0.0.15",
"node-sass": "^4.14.1",
"sass-loader": "^9.0.2",
"script-loader": "^0.7.2"
},
"eslintConfig": {
"root": true,

View File

@ -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/json/JsonTable";
import MsJsonCodeEdit from "../../../../common/json-schema/JsonEditor";
import MsDropdown from "../../../../common/components/MsDropdown";
import MsApiVariable from "../ApiVariable";
import MsApiBinaryVariable from "./ApiBinaryVariable";

View File

@ -0,0 +1,53 @@
<template>
<div id="app">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="模版" name="apiTemplate">
<div>
<json-schema-editor :schema.sync="schema" :is-mock="true"></json-schema-editor>
</div>
</el-tab-pane>
<el-tab-pane label="预览" name="preview">
<div style="min-height: 400px">
<pre>{{this.preview}}</pre>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import {schemaToJson} from './common';
export default {
name: 'App',
components: {},
data() {
return {
schema: {
type: 'object',
title: 'title',
properties: {
field_1: {
type: 'string'
}
}
},
preview: null,
activeName: "apiTemplate",
}
},
methods: {
handleClick() {
if (this.activeName === 'preview') {
console.log(this.schema)
this.preview = schemaToJson(this.schema);
}
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,31 @@
const Mock = require('mockjs');
const jsf = require('json-schema-faker');
jsf.extend('mock', function () {
return {
mock: function (xx) {
return Mock.mock(xx);
}
};
});
const defaultOptions = {
failOnInvalidTypes: false,
failOnInvalidFormat: false,
alwaysFakeOptionals: true,
useDefaultValue: true
};
export const schemaToJson = (schema, options = {}) => {
Object.assign(options, defaultOptions);
jsf.option(options);
let result;
try {
result = jsf.generate(schema);
} catch (err) {
result = err.message;
}
jsf.option(defaultOptions);
return result;
};

View File

@ -0,0 +1,82 @@
<template>
<el-dialog
title="基础设置(数组字段)"
width="700px"
v-bind="$attrs"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-row :gutter="15">
<el-form ref="elForm" :model="formData" size="small">
<el-col :span="12">
<el-form-item label="最小元素个数" prop="minItems">
<el-input-number
v-model="formData.minItems"
placeholder="请输入"
:min="0"
:step="1"
></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最大元素个数" prop="maxItems">
<el-input-number
v-model="formData.maxItems"
placeholder="请输入"
:max="100000"
:step="1"
></el-input-number>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</template>
<script>
import { getValidFormVal } from '../utils'
export default {
name: 'ArrayDialog',
inheritAttrs: false,
props: { initData: { type: Object, default: () => ({}) } },
data() {
return {
formData: {
minItems: undefined,
maxItems: undefined,
},
}
},
created() {},
methods: {
onOpen() {
const { minItems, maxItems } = this.initData
Object.assign(this.formData, { minItems, maxItems })
},
onClose() {
this.$refs['elForm'].resetFields()
},
close() {
this.$emit('update:visible', false)
},
handleConfirm() {
this.$refs['elForm'].validate((valid) => {
if (!valid) return
const newData = getValidFormVal(this.formData)
this.$jsEditorEvent.emit(`schema-update-${this.initData.editorId}`, {
eventType: 'save-setting',
...this.initData, //
newData, //
})
this.close()
})
},
},
}
</script>

View File

@ -0,0 +1,65 @@
<template>
<div>
<el-dialog :title="initData.title" :visible.sync="visible" width="30%">
<el-input
v-model="data"
type="textarea"
:rows="3"
placeholder="请输入内容"
style="margin-bottom: 15px"
></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
<el-button type="primary" @click="handleOk"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'BasicDialog',
props: {
visible: { type: Boolean, default: false },
initData: {
type: Object,
default: () => ({
title: '提示',
value: '',
}),
},
},
data() {
return {
dialogVisible: false,
data: '',
}
},
watch: {
initData: {
handler() {
this.data = this.initData.value
},
deep: true,
},
},
created() {},
methods: {
close() {
this.$emit('update:visible', false)
},
handleOk() {
this.initData.value = this.data
this.$jsEditorEvent.emit(`schema-update-${this.initData.editorId}`, {
eventType: 'save-showedit',
...this.initData,
})
this.close()
},
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,83 @@
<template>
<el-dialog
title="基础设置(布尔型字段)"
width="600px"
v-bind="$attrs"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-form ref="elForm" :model="formData" size="small" label-width="100px">
<el-form-item label="默认值:" prop="default">
<el-select
v-model="formData.default"
placeholder="请下拉选择"
clearable
:style="{ width: '60%' }"
>
<el-option
v-for="(item, index) in defaultOptions"
:key="index"
:label="item.label"
:value="item.value"
:disabled="item.disabled"
></el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</template>
<script>
import { getValidFormVal } from '../utils'
export default {
name: 'BooleanDialog',
inheritAttrs: false,
props: { initData: { type: Object, default: () => ({}) } },
data() {
return {
formData: {
default: undefined,
},
defaultOptions: [
{
label: 'true',
value: true,
},
{
label: 'false',
value: false,
},
],
}
},
created() {},
methods: {
onOpen() {
Object.assign(this.formData, { default: this.initData.default })
},
onClose() {
this.$refs['elForm'].resetFields()
},
close() {
this.$emit('update:visible', false)
},
handleConfirm() {
this.$refs['elForm'].validate((valid) => {
if (!valid) return
const newData = getValidFormVal(this.formData)
this.$jsEditorEvent.emit(`schema-update-${this.initData.editorId}`, {
eventType: 'save-setting',
...this.initData, //
newData, //
})
this.close()
})
},
},
}
</script>

View File

@ -0,0 +1,164 @@
<template>
<div>
<el-dialog
title="基础设置(数值型)"
width="700px"
v-bind="$attrs"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-row :gutter="15">
<el-form
ref="elForm"
:model="formData"
:rules="rules"
size="small"
>
<el-col :span="24">
<el-form-item label="默认值:" prop="default">
<el-input
v-model="formData.default"
type="number"
placeholder="请输入默认值"
:maxlength="15"
clearable
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最小值:" prop="minLength">
<el-input-number
v-model="formData.minLength"
placeholder="请输入"
:min="-9007199254740992"
:step="1"
></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最大值:" prop="maxLength">
<el-input-number
v-model="formData.maxLength"
placeholder="请输入"
:step="1"
:max="9007199254740992"
></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<el-row>
<el-col :span="3">
<div>
<label for>枚举</label>
<el-checkbox v-model="enableEnum">:</el-checkbox>
</div>
</el-col>
<el-col :span="21">
<el-form-item label-width="0" prop="enum">
<el-input
v-model="formData.enum"
type="textarea"
placeholder="请输入枚举,一行一个"
:maxlength="120"
:disabled="!enableEnum"
:autosize="{ minRows: 2, maxRows: 4 }"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col v-if="enableEnum" :span="24">
<el-form-item label="枚举描述:" prop="enumDesc">
<el-input
v-model="formData.enumDesc"
type="textarea"
placeholder="请输入枚举描述"
:maxlength="100"
:autosize="{ minRows: 2, maxRows: 4 }"
></el-input>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import compact from 'lodash/compact'
import {getValidFormVal} from '../utils'
export default {
name: 'NumberDialog',
inheritAttrs: false,
props: {initData: {type: Object, default: () => ({})}},
data() {
return {
enableEnum: false,
formData: {
default: undefined,
minLength: undefined,
maxLength: undefined,
enum: undefined,
enumDesc: undefined,
},
rules: {
default: [],
minLength: [],
maxLength: [],
innerScope: [],
enum: [],
enumDesc: [],
},
}
},
methods: {
onOpen() {
const {minLength, maxLength, enumDesc} = this.initData
let enumValue = this.initData.enum
if (enumValue) {
try {
enumValue = enumValue.join('\n')
this.enableEnum = true
} catch (e) {
this.$message({text: '枚举数据格式不对,将丢失', type: 'warning'})
enumValue = ''
}
}
Object.assign(
this.formData,
{minLength, maxLength, enumDesc},
{default: this.initData.default, enum: enumValue}
)
},
onClose() {
this.$refs['elForm'].resetFields()
},
close() {
this.$emit('update:visible', false)
},
handleConfirm() {
this.$refs['elForm'].validate((valid) => {
if (!valid) return
const newData = getValidFormVal(this.formData)
if (newData.enum) {
newData.enum = compact(newData.enum.split('\n'))
}
if (newData.default) {
newData.default = Number(newData.default)
}
this.$jsEditorEvent.emit(`schema-update-${this.initData.editorId}`, {
eventType: 'save-setting',
...this.initData, //
newData, //
})
this.close()
})
},
},
}
</script>

View File

@ -0,0 +1,78 @@
<template>
<el-dialog
title="基础设置(对象字段)"
width="600px"
v-bind="$attrs"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-form ref="elForm" :model="formData" size="small" label-width="100px">
<el-form-item label-width="0" prop="notEmpty" style="text-align: center">
<el-radio-group v-model="formData.notEmpty" size="medium">
<el-radio
v-for="(item, index) in notEmptyOptions"
:key="index"
:label="item.value"
:disabled="item.disabled"
>{{ item.label }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</template>
<script>
import { getValidFormVal } from '../utils'
export default {
name: 'ObjectDialog',
inheritAttrs: false,
props: { initData: { type: Object, default: () => ({}) } },
data() {
return {
formData: {
notEmpty: false,
},
notEmptyOptions: [
{
label: '可为空',
value: false,
},
{
label: '不允许为空',
value: true,
},
],
}
},
created() {},
methods: {
onOpen() {
Object.assign(this.formData, { notEmpty: this.initData.notEmpty })
},
onClose() {
this.$refs['elForm'].resetFields()
},
close() {
this.$emit('update:visible', false)
},
handleConfirm() {
this.$refs['elForm'].validate((valid) => {
if (!valid) return
const newData = getValidFormVal(this.formData)
this.$jsEditorEvent.emit(`schema-update-${this.initData.editorId}`, {
eventType: 'save-setting',
...this.initData, //
newData, //
})
this.close()
})
},
},
}
</script>

View File

@ -0,0 +1,43 @@
<template>
<div>
<el-dialog
title="RAW源码查看"
width="700px"
v-bind="$attrs"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<div class="sourcecode">
<s-json-editor :value="schema"></s-json-editor>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="close"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import SJsonEditor from '../json-editor/index'
export default {
name: 'RawDialog',
components: { SJsonEditor },
inheritAttrs: false,
props: {
schema: { type: Object, default: () => ({}) },
},
data() {
return {}
},
created() {},
methods: {
onOpen() {},
onClose() {},
close() {
this.$emit('update:visible', false)
},
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,153 @@
<template>
<div>
<el-dialog
title="基础设置(字符串)"
width="700px"
v-bind="$attrs"
v-on="$listeners"
@open="onOpen"
@close="onClose"
>
<el-row :gutter="15">
<el-form
ref="elForm"
:model="formData"
:rules="rules"
size="small">
<el-col :span="24">
<el-form-item label="默认值:" prop="default">
<el-input
v-model="formData.default"
placeholder="请输入默认值"
:maxlength="200"
clearable/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最小长度:" prop="minLength">
<el-input-number
v-model="formData.minLength"
placeholder="请输入"
:step="2"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="最大长度:" prop="maxLength">
<el-input-number
v-model="formData.maxLength"
placeholder="请输入"
:step="2"/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-row>
<el-col :span="3">
<div>
<label for>枚举</label>
<el-checkbox v-model="enableEnum">:</el-checkbox>
</div>
</el-col>
<el-col :span="21">
<el-form-item label-width="0" prop="enum">
<el-input
v-model="formData.enum"
type="textarea"
placeholder="请输入枚举,一行一个"
:maxlength="120"
:disabled="!enableEnum"
:autosize="{ minRows: 2, maxRows: 4 }"
></el-input>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col v-if="enableEnum" :span="24">
<el-form-item label="枚举描述:" prop="enumDesc">
<el-input
v-model="formData.enumDesc"
type="textarea"
placeholder="请输入枚举描述"
:maxlength="100"
:autosize="{ minRows: 2, maxRows: 4 }"
></el-input>
</el-form-item>
</el-col>
</el-form>
</el-row>
<div slot="footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleConfirm">确定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import compact from 'lodash/compact'
import { getValidFormVal } from '../utils'
export default {
name: 'StringDialog',
inheritAttrs: false,
props: { initData: { type: Object, default: () => ({}) } },
data() {
return {
enableEnum: false,
formData: {
default: undefined,
minLength: undefined,
maxLength: undefined,
enum: undefined,
enumDesc: undefined,
},
rules: {
default: [],
minLength: [],
maxLength: [],
innerScope: [],
enum: [],
enumDesc: [],
},
}
},
methods: {
onOpen() {
const { minLength, maxLength, enumDesc } = this.initData
let enumValue = this.initData.enum
if (enumValue) {
try {
enumValue = enumValue.join('\n')
this.enableEnum = true
} catch (e) {
this.$message({ text: '枚举数据格式不对,将丢失', type: 'warning' })
enumValue = ''
}
}
Object.assign(
this.formData,
{ minLength, maxLength, enumDesc },
{ default: this.initData.default, enum: enumValue }
)
},
onClose() {
this.$refs['elForm'].resetFields()
},
close() {
this.$emit('update:visible', false)
},
handleConfirm() {
this.$refs['elForm'].validate((valid) => {
if (!valid) return
const newData = getValidFormVal(this.formData)
if (newData.enum) {
newData.enum = compact(newData.enum.split('\n'))
}
this.$jsEditorEvent.emit(`schema-update-${this.initData.editorId}`, {
eventType: 'save-setting',
...this.initData, //
newData, //
})
this.close()
})
},
},
}
</script>

View File

@ -0,0 +1,17 @@
import BasicDialog from './BasicDialog';
import StringDialog from './StringDialog';
import NumberDialog from './NumberDialog';
import ArrayDialog from './ArrayDialog';
import BooleanDialog from './BooleanDialog';
import ObjectDialog from './ObjectDialog';
import RawDialog from './RawDialog';
export {
BasicDialog,
StringDialog,
NumberDialog,
ArrayDialog,
BooleanDialog,
ObjectDialog,
RawDialog
};

View File

@ -0,0 +1,48 @@
/**
* @author: giscafer ,https://github.com/giscafer
* @date: 2020-05-21 17:21:29
* @description: 用一个Vue实例封装事件常用的方法并赋值给全局的变量以便在任何一个组件都可调用这些方法来实现全局事件管理
*
* 使用如下
* mounted(){
this.$jsEditorEvent.on('change_value',id);
this.$jsEditorEvent.emit('change_value',1);
...
}
*/
import Vue from 'vue';
const eventHub = new Vue({
methods: {
on(...args) {
this.$on.apply(this, args);
},
emit(...args) {
this.$emit.apply(this, args);
},
off(...args) {
this.$off.apply(this, args);
},
once(...args) {
this.$once.apply(this, args);
},
},
});
/* const CustomEventPlugin = V =>
Object.defineProperty(V.prototype, '$event', {
value: eventHub,
writable: true
}); */
const CustomEventPlugin = {
install: function(V) {
Object.defineProperty(V.prototype, '$jsEditorEvent', {
value: eventHub,
writable: true,
});
},
};
export default CustomEventPlugin;

View File

@ -0,0 +1,17 @@
import JsonSchemaEditor from './json-schema-editor.vue';
import CustomEventPlugin from './event';
const install = function(Vue) {
Vue.use(CustomEventPlugin);
Vue.component(JsonSchemaEditor.name, JsonSchemaEditor);
};
JsonSchemaEditor.install = install;
/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue);
}
export default JsonSchemaEditor;

View File

@ -0,0 +1,8 @@
import JsonEditor from './src/json-editor';
/* istanbul ignore next */
JsonEditor.install = function(Vue) {
Vue.component(JsonEditor.name, JsonEditor);
};
export default JsonEditor;

View File

@ -0,0 +1,90 @@
<template>
<div class="json-editor">
<textarea ref="textarea" />
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/addon/lint/lint.css'
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/idea.css'
import 'codemirror/theme/rubyblue.css'
require('script-loader!jsonlint')
import 'codemirror/mode/javascript/javascript'
import 'codemirror/addon/lint/lint'
import 'codemirror/addon/lint/json-lint'
export default {
name: 'SJsonEditor',
props: {
value: {
type: Object,
default: () => ({}),
},
readonly: {
type: Boolean,
default: true,
},
theme: {
type: String,
default: 'idea',
},
},
data() {
return {
jsonEditor: false,
}
},
watch: {
value(value) {
const editorValue = this.jsonEditor.getValue()
if (value !== editorValue) {
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
}
},
theme() {
this.jsonEditor.setOption({
theme: this.theme,
})
},
},
mounted() {
this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, {
lineNumbers: true,
mode: 'application/json',
gutters: ['CodeMirror-lint-markers'],
theme: this.theme || 'idea',
readonly: this.readonly ? 'nocursor' : false,
lint: true,
})
this.jsonEditor.setValue(JSON.stringify(this.value, null, 2))
this.jsonEditor.on('change', (cm) => {
this.$emit('changed', cm.getValue())
this.$emit('input', cm.getValue())
})
},
methods: {
getValue() {
return this.jsonEditor.getValue()
},
},
}
</script>
<style scoped>
.json-editor {
height: 100%;
position: relative;
}
.json-editor >>> .CodeMirror {
height: auto;
min-height: 300px;
}
.json-editor >>> .CodeMirror-scroll {
min-height: 300px;
}
.json-editor >>> .cm-s-rubyblue span.cm-string {
color: #f08047;
}
</style>

View File

@ -0,0 +1,617 @@
<template>
<div style="min-height: 400px;">
<el-button
v-if="showRaw"
type="primary"
size="mini"
style="margin-bottom: 10px"
@click="handleReqBodyRaw"
>RAW查看</el-button
>
<div class="json-schema-vue-editor">
<el-row type="flex" align="middle">
<el-col :span="8" class="col-item name-item col-item-name">
<el-row type="flex" justify="space-around" align="middle">
<el-col :span="2" class="down-style-col">
<span
v-if="schemaData.type === 'object'"
class="down-style"
@click="handleClickIcon"
>
<i v-if="show" class="el-icon-caret-bottom icon-object"></i>
<i v-if="!show" class="el-icon-caret-right icon-object"></i>
</span>
</el-col>
<el-col :span="20">
<el-input disabled value="root" size="small" />
</el-col>
<el-col :span="2" style="text-align: center">
<el-tooltip placement="top" content="全选">
<el-checkbox
:checked="checked"
:disabled="disabled"
@change="changeCheckBox"
/>
</el-tooltip>
</el-col>
</el-row>
</el-col>
<el-col :span="3" class="col-item col-item-type">
<el-select
:value="schemaData.type"
:disabled="schemaData.disabled && !schemaData.canChangeType"
class="type-select-style"
size="small"
@change="handleChangeType2($event)"
>
<el-option
v-for="item in schemaTypes"
:key="item"
:value="item"
:label="item"
></el-option>
</el-select>
</el-col>
<el-col v-if="isMock" :span="3" class="col-item col-item-mock">
<MockSelect
:schema="schemaData"
@showEdit="handleShowEdit"
@change="handleChangeMock"
/>
</el-col>
<el-col
v-if="showTitle"
:span="isMock ? 4 : 5"
class="col-item col-item-mock"
>
<el-input
v-model="schemaData.title"
placeholder="标题"
:disabled="schemaData.disabled"
size="small"
>
<i
slot="append"
class="el-icon-edit"
@click="
handleSchemaUpdateEvent({
eventType: 'show-edit',
field: 'title',
prefix: ['properties'],
isRoot: true,
})
"
></i>
</el-input>
</el-col>
<el-col
v-if="!showTitle && showDefaultValue"
:span="isMock ? 4 : 5"
class="col-item col-item-mock"
>
<el-input
v-model="schemaData.default"
placeholder="默认值"
size="small"
:disabled="
schemaData.type === 'object' ||
schemaData.type === 'array' ||
schemaData.disabled
"
>
<i
slot="append"
class="el-icon-edit"
@click="
handleSchemaUpdateEvent({
eventType: 'show-edit',
field: 'default',
prefix: ['properties'],
isRoot: true,
})
"
></i>
</el-input>
</el-col>
<el-col :span="isMock ? 4 : 5" class="col-item col-item-desc">
<el-input
v-model="schemaData.description"
placeholder="备注"
size="small"
:disabled="schemaData.disabled"
>
<i
slot="append"
class="el-icon-edit"
@click="
handleSchemaUpdateEvent({
eventType: 'show-edit',
field: 'description',
prefix: ['properties'],
isRoot: true,
})
"
></i>
</el-input>
</el-col>
<el-col :span="2" class="col-item col-item-setting">
<span
class="adv-set"
@click="
handleSchemaUpdateEvent({
eventType: 'setting',
schemaType: schemaData.type,
prefix: ['properties'],
isRoot: true,
})
"
>
<el-tooltip placement="top" content="高级设置">
<i class="el-icon-setting"></i>
</el-tooltip>
</span>
<span
v-if="schemaData.type === 'object'"
@click="
handleSchemaUpdateEvent({
eventType: 'add-field',
isChild: false,
prefix: ['properties'],
})
"
>
<el-tooltip placement="top" content="添加子节点">
<i class="el-icon-plus plus"></i>
</el-tooltip>
</span>
</el-col>
</el-row>
<schema-json
v-if="show"
:data="schemaData"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
<!-- RAW弹窗 -->
<RawDialog
v-if="showRaw"
:visible.sync="rawDialogVisible"
:schema="schemaData"
/>
<!-- 高级设置弹窗 -->
<BasicDialog
:visible.sync="basicDialogVisible"
:init-data="basicModalData"
/>
<StringDialog
:visible.sync="settingDialogVisible.string"
:init-data="settingModalData"
/>
<NumberDialog
:visible.sync="settingDialogVisible.number"
:init-data="settingModalData"
/>
<NumberDialog
:visible.sync="settingDialogVisible.integer"
:init-data="settingModalData"
/>
<ArrayDialog
:visible.sync="settingDialogVisible.array"
:init-data="settingModalData"
/>
<BooleanDialog
:visible.sync="settingDialogVisible.boolean"
:init-data="settingModalData"
/>
<ObjectDialog
:visible.sync="settingDialogVisible.object"
:init-data="settingModalData"
/>
</div>
</div>
</template>
<script>
import set from 'lodash/set'
import get from 'lodash/get'
import unset from 'lodash/unset'
import cloneDeep from 'lodash/cloneDeep'
import SchemaJson from './schema'
import MockSelect from './mock'
import './jsonschema.scss'
import {
BasicDialog,
StringDialog,
NumberDialog,
ArrayDialog,
BooleanDialog,
ObjectDialog,
RawDialog,
} from './dialog'
import {
SCHEMA_TYPE,
log,
JSONPATH_JOIN_CHAR,
defaultSchema,
uuid,
defaultInitSchemaData,
handleSchemaRequired,
cloneObject,
deleteData,
} from './utils'
export default {
name: 'JsonSchemaEditor',
components: {
MockSelect,
SchemaJson,
BasicDialog,
StringDialog,
NumberDialog,
ArrayDialog,
BooleanDialog,
ObjectDialog,
RawDialog,
},
props: {
schema: { type: Object, default: () => {} },
isMock: { type: Boolean, default: false },
showTitle: { type: Boolean, default: false },
showDefaultValue: { type: Boolean, default: false },
showRaw: { type: Boolean, default: false },
},
data() {
const visibleObj = {}
SCHEMA_TYPE.map((type) => {
visibleObj[type] = false
})
const initSchema = this.schema || defaultInitSchemaData
return {
editorId: uuid(),
checked: false,
disabled: false,
show: true,
schemaTypes: SCHEMA_TYPE,
schemaData: initSchema,
rawDialogVisible: false,
basicDialogVisible: false,
basicModalData: { title: '', value: '' },
settingDialogVisible: visibleObj,
settingModalData: {},
}
},
watch: {
schemaData: {
handler(newVal) {
log(this, 'watch', newVal)
},
deep: true,
},
},
mounted() {
log(this, this.schemaData)
this.$jsEditorEvent.on(
`schema-update-${this.editorId}`,
this.handleSchemaUpdateEvent
)
},
beforeDestroy() {
this.$jsEditorEvent.off(
`schema-update-${this.editorId}`,
this.handleSchemaUpdateEvent
)
},
methods: {
handleSchemaUpdateEvent(options) {
const { eventType, ...opts } = options
switch (eventType) {
case 'add-field':
this.addFieldAction(opts)
break
case 'delete-field':
this.deleteFieldAction(opts)
break
case 'update-field-name':
this.updateFieldNameAction(opts)
break
case 'schema-type':
this.handleChangeType(opts)
break
case 'show-edit':
this.handleShowEdit(opts)
break
case 'save-showedit':
this.handleSaveShowEdit(opts)
break
case 'setting':
this.handleSettingAction(opts)
break
case 'save-setting':
this.handleSaveSetting(opts)
break
case 'toggle-required':
this.enableRequireAction(opts)
break
default:
break
}
},
handleClickIcon() {
this.show = !this.show
},
changeCheckBox(e) {
this.requireAllAction({ required: e, value: this.schemaData })
},
requireAllAction(opts) {
const { value, required } = opts
const cloneSchema = cloneObject(value)
handleSchemaRequired(cloneSchema, required)
this.forceUpdate(cloneSchema)
this.handleEmitChange(cloneSchema)
},
enableRequireAction(opts) {
const { prefix, name, required } = opts
const prefixCopy = cloneDeep(prefix)
prefixCopy.pop()
const parentKeys = [...prefixCopy]
const parentPrefix = parentKeys.join(JSONPATH_JOIN_CHAR)
const cloneSchema = cloneDeep(this.schemaData)
let parentData = null
if (!parentPrefix) {
//
parentData = cloneSchema
} else {
parentData = get(cloneSchema, parentPrefix)
}
const requiredData = [].concat(parentData.required || [])
const index = requiredData.indexOf(name)
//
if (!required && index >= 0) {
requiredData.splice(index, 1)
parentKeys.push('required')
if (requiredData.length === 0) {
deleteData(cloneSchema, parentKeys)
} else {
set(cloneSchema, parentKeys, requiredData)
}
} else if (required && index === -1) {
//
requiredData.push(name)
parentKeys.push('required')
set(cloneSchema, parentKeys, requiredData)
}
this.forceUpdate(cloneSchema)
this.handleEmitChange(cloneSchema)
},
/**
* 处理新增字段
* @param isChild 新增子节点
* @param action 字段和路径
*/
addFieldAction(opts) {
log(this, opts)
const { isChild, name, prefix } = opts
let parentPrefix = ''
let requirePrefix = []
if (isChild) {
const tempArr = [].concat(prefix, name)
parentPrefix = tempArr.concat('properties').join(JSONPATH_JOIN_CHAR)
requirePrefix = [...tempArr]
} else {
parentPrefix = prefix.join(JSONPATH_JOIN_CHAR)
const tempPrefix = [].concat(prefix)
tempPrefix.pop()
requirePrefix = tempPrefix
}
log('addFieldAction>>>', parentPrefix, '\n\t')
let newPropertiesData = {}
const ranName = 'field_' + uuid()
const propertiesData = get(this.schemaData, parentPrefix)
newPropertiesData = Object.assign({}, propertiesData)
newPropertiesData[ranName] = defaultSchema.string
const cloneSchema = cloneDeep(this.schemaData)
set(cloneSchema, parentPrefix, newPropertiesData)
// add required
let pRequiredData = null
if (!requirePrefix.length) {
//
pRequiredData = cloneSchema
} else {
pRequiredData = get(cloneSchema, requirePrefix)
}
const requiredData = [].concat(pRequiredData.required || [])
requiredData.push(ranName)
requirePrefix.push('required')
set(cloneSchema, requirePrefix, requiredData)
// update schema
this.schemaData = cloneSchema
this.forceUpdate(cloneSchema)
this.handleEmitChange(cloneSchema)
},
//
deleteFieldAction(opts) {
const { name, prefix } = opts
const curFieldPath = [].concat(prefix, name).join(JSONPATH_JOIN_CHAR)
// console.log(curFieldPath)
const cloneSchema = cloneDeep(this.schemaData)
unset(cloneSchema, curFieldPath)
this.schemaData = cloneSchema
this.forceUpdate()
this.handleEmitChange(cloneSchema)
},
//
updateFieldNameAction(opts) {
log(this, opts)
const { value, name, prefix } = opts
let requirePrefix = []
const prefixCopy = cloneDeep(prefix)
prefixCopy.pop()
requirePrefix = prefixCopy // required
const parentPrefix = prefix.join(JSONPATH_JOIN_CHAR)
const curFieldPath = prefix.concat(name).join(JSONPATH_JOIN_CHAR)
const cloneSchema = cloneDeep(this.schemaData)
const propertiesData = get(cloneSchema, curFieldPath) //
unset(cloneSchema, curFieldPath) //
set(cloneSchema, `${parentPrefix}.${value}`, propertiesData) //
// update required name
let pRequiredData = null
if (!requirePrefix.length) {
//
pRequiredData = cloneSchema
} else {
pRequiredData = get(cloneSchema, requirePrefix)
}
let requiredData = [].concat(pRequiredData.required || [])
requiredData = requiredData.map((item) => {
if (item === name) return value
return item
})
requirePrefix.push('required')
set(cloneSchema, requirePrefix, requiredData)
this.schemaData = cloneSchema
this.forceUpdate()
this.handleEmitChange(cloneSchema)
},
// root
handleChangeType2(value) {
this.schemaData.type = value
const parentDataItem = this.schemaData.description
? { description: this.schemaData.description }
: {}
const newParentDataItem = defaultSchema[value]
const newParentData = Object.assign({}, newParentDataItem, parentDataItem)
this.schemaData = newParentData
this.handleEmitChange(this.schemaData)
},
// schema
handleChangeType(opts) {
log(this, opts, 2)
const { value, name, prefix } = opts
const parentPrefix = [].concat(prefix, name)
const cloneSchema = cloneDeep(this.schemaData)
const parentData = get(cloneSchema, parentPrefix)
const newParentDataItem = defaultSchema[value] // schema
//
const parentDataItem = parentData.description
? { description: parentData.description }
: {}
const newParentData = Object.assign({}, newParentDataItem, parentDataItem)
set(cloneSchema, parentPrefix, newParentData)
this.schemaData = cloneSchema
this.forceUpdate()
this.handleEmitChange(cloneSchema)
},
// title & description
handleShowEdit(opts) {
const { field, name, prefix, isRoot } = opts
log(this, 'handleShowEdit', name, prefix)
let parentData
if (isRoot) {
parentData = this.schemaData
} else {
const parentPrefix = [].concat(prefix, name)
parentData = get(this.schemaData, parentPrefix)
}
// disable return
if (
(field === 'default' && parentData.type === 'array') ||
parentData.type === 'object'
) {
return
}
this.basicDialogVisible = true
Object.assign(this.basicModalData, {
title:
field === 'title' ? '标题' : field === 'default' ? '默认值' : '描述',
value: parentData[field],
editorId: this.editorId,
...opts,
})
},
handleSaveShowEdit(opts) {
const { value, field, name, prefix, isRoot } = opts
// console.log(field, value)
let parentPrefix
const cloneSchema = cloneDeep(this.schemaData)
if (isRoot) {
cloneSchema[field] = value
} else {
parentPrefix = [].concat(prefix, name, field)
set(cloneSchema, parentPrefix, value)
}
this.schemaData = cloneSchema
this.forceUpdate()
this.handleEmitChange(cloneSchema)
},
//
handleSettingAction(opts) {
const { schemaType, name, prefix, isRoot } = opts
// console.log(schemaType)
this.settingDialogVisible[schemaType] = true
let parentData
if (isRoot) {
parentData = this.schemaData
} else {
const parentPrefix = [].concat(prefix, name)
parentData = get(this.schemaData, parentPrefix)
}
this.settingModalData = {
schemaType,
name,
isRoot,
prefix,
editorId: this.editorId,
...parentData,
}
},
// schema
handleSaveSetting(opts) {
const { name, prefix, newData, isRoot } = opts
const cloneSchema = cloneDeep(this.schemaData)
if (isRoot) {
Object.assign(cloneSchema, { ...newData })
} else {
const parentPrefix = [].concat(prefix, name)
const oldData = get(cloneSchema, parentPrefix)
set(cloneSchema, parentPrefix, { ...oldData, ...newData })
}
this.schemaData = cloneSchema
this.forceUpdate()
this.handleEmitChange(cloneSchema)
},
handleChangeMock() {},
handleReqBodyRaw() {
this.rawDialogVisible = true
this.forceUpdate()
},
//
forceUpdate(data) {
const temp = data || this.schemaData
this.schemaData = {}
this.$nextTick(() => {
this.schemaData = temp
})
},
handleEmitChange(schema) {
// console.log(schema)
this.$emit('schema-change', schema)
this.$emit('update:schema', schema)
},
},
}
</script>

View File

@ -0,0 +1,149 @@
.json-schema-vue-editor {
cursor: pointer;
.el-input--medium {
height: 36px;
}
.el-input.is-disabled {
background-color: #f5f7fa;
border-color: #dfe4ed;
color: #c0c4cc;
cursor: not-allowed;
}
.hidden {
display: none;
}
}
.json-schema-vue-editor .option-formStyle {
/* padding-left: 25px; */
padding-top: 8px;
}
.json-schema-vue-editor .required-icon {
font-size: 1em;
color: red;
font-weight: bold;
padding-left: 5px;
}
.json-schema-vue-editor .object-style {
/* border-left: 2px dotted gray; */
/* padding-left: 8px; */
padding-top: 6px;
/* margin-left: 20px; */
margin-top: 8px;
}
.json-schema-vue-editor .col-item-type {
text-align: center;
}
.json-schema-vue-editor .down-style {
cursor: pointer;
}
.json-schema-vue-editor .col-item-desc {
text-align: center;
}
.json-schema-vue-editor .col-item-mock {
text-align: center;
padding-right: 6px;
}
.json-schema-vue-editor .col-item-setting {
padding-left: 6px;
cursor: pointer;
}
.json-schema-vue-editor .plus {
color: #2395f1;
}
.json-schema-vue-editor .close {
color: #ff561b;
}
.json-schema-vue-editor .array-type {
margin-top: 8px;
}
.json-schema-vue-editor .delete-item {
padding-right: 8px;
}
.json-schema-vue-editor .object-style .name-item .ant-input-group-addon {
background-color: unset;
border: unset;
}
.json-schema-vue-editor
.object-style
.name-item
.ant-input-group
> .ant-input:first-child,
.ant-input-group-addon:first-child {
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
}
.json-schema-vue-editor .icon-object {
color: #0d1b3ea6;
font-weight: 400;
font-size: 12px;
}
.json-schema-vue-editor .down-style-col {
width: 10px;
}
.json-schema-vue-editor .wrapper {
padding-left: 8px;
}
/* .schema-content {
margin-left: 20px;
} */
.json-schema-vue-editor .adv-set {
padding-right: 8px;
color: #00a854;
}
.json-schema-vue-editor .type-select-style {
width: 90%;
}
.json-schema-vue-editor-import-modal .ant-tabs-nav .ant-tabs-tab {
height: auto;
}
.json-schema-vue-editor-adv-modal .other-row {
margin-bottom: 16px;
}
.json-schema-vue-editor-adv-modal .other-label {
text-align: right;
padding-right: 8px;
}
.json-schema-vue-editor-adv-modal .default-setting {
font-size: 16px;
font-weight: 400;
margin-bottom: 16px;
border-left: 3px solid #2395f1;
padding-left: 8px;
}
.json-schema-vue-editor-adv-modal .ant-modal-body {
min-height: 400px;
}
.json-schema-vue-editor-adv-modal .ant-modal-body .ace_editor {
min-height: 350px;
}
.json-schema-vue-editor-adv-modal-select .format-items-title {
color: #999;
position: absolute;
right: 16px;
}

View File

@ -0,0 +1,237 @@
<template>
<el-dialog :title="$t('api_test.request.parameters_advance')"
:visible.sync="itemValueVisible"
class="advanced-item-value"
width="70%">
<el-tabs tab-position="top" style="height: 50vh;" @tab-click="selectTab">
<el-tab-pane :label="$t('api_test.request.parameters_advance_mock')">
<el-row type="flex" :gutter="20">
<el-col :span="6" class="col-height">
<div>
<el-input size="small" v-model="filterText"
:placeholder="$t('api_test.request.parameters_mock_filter_tips')"/>
<el-tree class="filter-tree" ref="tree" :data="mockFuncs" :props="treeProps"
default-expand-all @node-click="selectVariable"
:filter-node-method="filterNode"></el-tree>
</div>
</el-col>
<el-col :span="6" v-for="(itemFunc, itemIndex) in mockVariableFuncs" :key="itemIndex">
<div v-for="(func, funcIndex) in funcs"
:key="`${itemIndex}-${funcIndex}`">
<el-row>
<el-col :span="12">
<el-radio size="mini" v-model="itemFunc.name" :label="func.name"
@change="methodChange(itemFunc, func)"/>
</el-col>
<el-col :span="12" v-if="itemFunc.name === func.name">
<div v-for="(p, pIndex) in itemFunc.params" :key="`${itemIndex}-${funcIndex}-${pIndex}`">
<el-input :placeholder="p.name" size="mini" v-model="p.value" @change="showPreview"/>
</div>
</el-col>
</el-row>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane :label="$t('api_test.variable')">
<el-row>
<el-col :span="6" class="col-height">
<div v-if="preRequestParams">
<p>{{ $t('api_test.request.parameters_pre_request') }}</p>
<el-tree :data="preRequestParams" :props="treeProps" @node-click="selectVariable"></el-tree>
</div>
</el-col>
<el-col :span="18" class="col-height">
<div>
<h1>{{ $t('api_test.request.jmeter_func') }}</h1>
<el-table border :data="jmeterFuncs" class="adjust-table table-content" height="400">
<el-table-column prop="type" label="Type" width="150"/>
<el-table-column prop="name" label="Functions" width="250"/>
<el-table-column prop="description" label="Description"/>
</el-table>
</div>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
<el-form>
<el-form-item>
<el-input :placeholder="valueText" size="small"
v-model="itemValue"/>
</el-form-item>
</el-form>
<div style="padding-top: 10px;">
<el-row type="flex" align="middle">
<el-col :span="12">
<el-button size="small" type="primary" plain @click="saveAdvanced()">
{{ $t('commons.save') }}
</el-button>
<el-button size="small" type="info" plain @click="addFunc()" v-if="currentTab === 0">
{{ $t('api_test.request.parameters_advance_add_func') }}
</el-button>
<el-button size="small" type="success" plain @click="showPreview()" v-if="currentTab === 0">
{{ $t('api_test.request.parameters_preview') }}
</el-button>
</el-col>
<el-col>
<div> {{ itemValuePreview }}</div>
</el-col>
</el-row>
</div>
</el-dialog>
</template>
<script>
import {calculate} from "./Calculate";
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
export default {
name: "MsApiVariableAdvance",
props: {
parameters: Array,
environment: Object,
currentItem: Object,
},
data() {
return {
itemValueVisible: false,
filterText: '',
environmentParams: [],
scenarioParams: [],
Scenario: {},
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);
},
},
methods: {
open() {
this.itemValueVisible = true;
},
prepareData() {
},
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(",");
}
});
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;
}
}
}
}
this.mockVariableFuncs.push({name: '', params: []});
},
saveAdvanced() {
this.currentItem.mock = this.itemValue;
this.itemValueVisible = false;
this.mockVariableFuncs = [];
this.$emit('advancedRefresh', this.itemValue);
}
}
}
</script>
<style scoped>
.col-height {
height: 40vh;
overflow: auto;
}
</style>

View File

@ -0,0 +1,31 @@
import Mock from "mockjs";
import {funcFilters} from "@/common/js/func-filter";
export const calculate = function (itemValue) {
if (!itemValue) {
return;
}
try {
if (itemValue.trim().startsWith("${")) {
// jmeter 内置函数不做处理
return itemValue;
}
let funcs = itemValue.split("|");
let value = Mock.mock(funcs[0].trim());
if (funcs.length === 1) {
return value;
}
for (let i = 1; i < funcs.length; i++) {
let func = funcs[i].trim();
let args = func.split(":");
let strings = [];
if (args[1]) {
strings = args[1].split(",");
}
value = funcFilters[args[0].trim()](value, ...strings);
}
return value;
} catch (e) {
return itemValue;
}
}

View File

@ -0,0 +1,93 @@
<template>
<div>
<el-autocomplete
size="small"
:disabled="false"
class="input-with-autocomplete"
v-model="schema.mock"
: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()"></i>
</el-autocomplete>
<ms-advance ref="variableAdvance" :current-item="schema"/>
</div>
</template>
<script>
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
import MsAdvance from "./Advance";
export default {
name: 'MockSelect',
components: {MsAdvance},
props: {
schema: {
type: Object,
default: () => {
}
},
mock: {
type: Array,
default: () => []
}
},
data() {
return {
mockValue: ''
}
},
created() {
},
mounted() {
},
methods: {
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 () {
},
advanced() {
this.$refs.variableAdvance.open();
},
showEdit() {
this.$emit('showEdit')
},
handleChange(e) {
this.$emit('change', e)
},
querySearchAsync(queryString, cb) {
const arr = this.mock || []
const results = queryString
? arr.filter(this.createStateFilter(queryString))
: arr
cb(results)
},
createStateFilter(queryString) {
return state => {
return (
state.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
)
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,45 @@
<template>
<el-tooltip placement="top" content="添加兄弟/子节点">
<el-dropdown trigger="click">
<i class="el-icon-plus plus"></i>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>
<span @click="addFieldAction({type:'add-field',isChild:false})">兄弟节点</span>
</el-dropdown-item>
<el-dropdown-item>
<span @click="addFieldAction({type:'add-field',isChild:true})">子节点</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-tooltip>
</template>
<script>
export default {
name: 'DropPlus',
components: {},
props: {
prefix: {
type: Array,
default: () => []
},
name: {
type: String,
default: ''
}
},
data() {
return {}
},
created() {},
mounted() {},
methods: {
addFieldAction(...args) {
this.$emit('add-field', ...args)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,224 @@
<template>
<div class="array-type">
<el-row type="flex" align="middle">
<el-col
:span="8"
class="col-item name-item col-item-name"
:style="tagPaddingLeftStyle"
>
<el-row type="flex" justify="space-around" align="middle">
<el-col :span="2" class="down-style-col">
<span
v-if="items.type === 'object'"
class="down-style"
@click="handleClickIcon"
>
<i v-if="!showIcon" class="el-icon-caret-bottom icon-object"></i>
<i v-else class="el-icon-caret-right icon-object"></i>
</span>
</el-col>
<el-col :span="20">
<el-input disabled value="Items" size="small" />
</el-col>
<el-col :span="2" style="text-align: center">
<el-tooltip placement="top" content="全选">
<el-checkbox disabled />
</el-tooltip>
</el-col>
</el-row>
</el-col>
<el-col :span="3" class="col-item col-item-type">
<el-select
:value="items.type"
size="small"
class="type-select-style"
@change="handleChangeType"
>
<el-option
v-for="item in schemaTypes"
:key="item"
:value="item"
:label="item"
></el-option>
</el-select>
</el-col>
<el-col v-if="isMock" :span="3" class="col-item col-item-mock">
<MockSelect
:schema="items"
@showEdit="handleAction({ eventType: 'mock-edit' })"
@change="handleChangeMock"
/>
</el-col>
<el-col
v-if="showTitle"
:span="isMock ? 4 : 5"
class="col-item col-item-mock"
>
<el-input v-model="items.title" placeholder="标题" size="small">
<i
slot="append"
class="el-icon-edit"
@click="handleAction({ eventType: 'show-edit', field: 'title' })"
></i>
</el-input>
</el-col>
<el-col
v-if="!showTitle && showDefaultValue"
:span="isMock ? 4 : 5"
class="col-item col-item-mock"
>
<el-input v-model="items.default" placeholder="默认值" size="small">
<i
slot="append"
class="el-icon-edit"
@click="handleAction({ eventType: 'show-edit', field: 'default' })"
></i>
</el-input>
</el-col>
<el-col :span="isMock ? 4 : 5" class="col-item col-item-desc">
<el-input v-model="items.description" placeholder="备注" size="small">
<i
slot="append"
class="el-icon-edit"
@click="
handleAction({ eventType: 'show-edit', field: 'description' })
"
></i>
</el-input>
</el-col>
<el-col :span="isMock ? 2 : 3" class="col-item col-item-setting">
<span
class="adv-set"
@click="
handleAction({ eventType: 'setting', schemaType: items.type })
"
>
<el-tooltip placement="top" content="高级设置">
<i class="el-icon-setting"></i>
</el-tooltip>
</span>
<span
v-if="items.type === 'object'"
@click="handleAction({ eventType: 'add-field', isChild: true })"
>
<el-tooltip placement="top" content="添加子节点">
<i class="el-icon-plus plus"></i>
</el-tooltip>
</span>
</el-col>
</el-row>
<div class="option-formStyle">
<template v-if="items.type === 'array'">
<SchemaArray
:prefix="prefixArray"
:data="items"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
</template>
<template v-if="items.type === 'object' && !showIcon">
<SchemaObject
:prefix="nameArray"
:data="items"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
</template>
</div>
</div>
</template>
<script>
import isUndefined from 'lodash/isUndefined'
import MockSelect from '../mock'
import SchemaObject from './SchemaObject'
import { SCHEMA_TYPE } from '../utils'
export default {
name: 'SchemaArray',
components: { MockSelect, SchemaObject },
props: {
isMock: {
type: Boolean,
default: false,
},
showTitle: {
type: Boolean,
default: false,
},
showDefaultValue: { type: Boolean, default: false },
editorId: {
type: String,
default: 'editor_id',
},
name: {
type: String,
default: '',
},
prefix: {
type: Array,
default: () => [],
},
data: {
type: Object,
default: () => {},
},
action: {
type: Function,
default: () => () => {},
},
},
data() {
return {
tagPaddingLeftStyle: {},
schemaTypes: SCHEMA_TYPE,
items: this.data.items,
showIcon: false,
}
},
computed: {
nameArray() {
return [].concat(this.prefixArray, 'properties')
},
prefixArray() {
return [].concat(this.prefix, 'items')
},
},
beforeMount() {
const length = this.prefix.filter((name) => name !== 'properties').length
this.tagPaddingLeftStyle = {
paddingLeft: `${20 * (length + 1)}px`,
}
},
methods: {
isUndefined() {
return isUndefined
},
handleClickIcon() {
this.showIcon = !this.showIcon
},
handleAction(opts) {
const { prefix, name } = this
this.$jsEditorEvent.emit(`schema-update-${this.editorId}`, {
prefix,
name: name || 'items',
...opts,
})
},
handleChangeMock() {},
handleChangeType(value) {
console.log(value)
this.handleAction({ eventType: 'schema-type', value })
},
},
}
</script>

View File

@ -0,0 +1,290 @@
<template>
<div>
<el-row type="flex" align="middle">
<el-col
:span="8"
class="col-item name-item col-item-name"
:style="tagPaddingLeftStyle"
>
<el-row type="flex" justify="space-around" align="middle">
<el-col :span="2" class="down-style-col">
<span
v-if="value.type === 'object'"
class="down-style"
@click="handleClickIcon"
>
<i v-if="showIcon" class="el-icon-caret-bottom icon-object"></i>
<i v-if="!showIcon" class="el-icon-caret-right icon-object"></i>
</span>
</el-col>
<el-col :span="20" class="el-input--small">
<input
size="small"
class="el-input el-input__inner"
:class="{ 'is-disabled': value.disabled }"
:value="name"
:disabled="value.disabled"
@change="handleNameChange"
/>
</el-col>
<el-col :span="2" style="text-align: center">
<el-tooltip placement="top" content="是否必须">
<el-checkbox
:checked="
(data.required && data.required.indexOf(name) != -1) || false
"
@change="handleEnableRequire"
></el-checkbox>
</el-tooltip>
</el-col>
</el-row>
</el-col>
<el-col :span="3" class="col-item col-item-type">
<el-select
size="small"
:value="value.type"
:disabled="value.disabled && !value.canChangeType"
class="type-select-style"
@change="handleChangeType"
>
<el-option
v-for="item in schemaTypes"
:key="item"
:value="item"
:label="item"
></el-option>
</el-select>
</el-col>
<el-col v-if="isMock" :span="3" class="col-item col-item-mock">
<MockSelect
:schema="value"
@showEdit="handleAction({ eventType: 'mock-edit' })"
@change="handleChangeMock"
/>
</el-col>
<el-col
v-if="showTitle"
:span="isMock ? 4 : 5"
class="col-item col-item-mock"
>
<el-input
v-model="value.title"
:disabled="value.disabled"
size="small"
placeholder="标题"
>
<i
slot="append"
class="el-icon-edit"
@click="handleAction({ eventType: 'show-edit', field: 'title' })"
></i>
</el-input>
</el-col>
<!-- 默认值输入框 -->
<el-col
v-if="!showTitle && showDefaultValue"
:span="isMock ? 4 : 5"
class="col-item col-item-mock"
>
<el-input
v-model.trim="value.default"
placeholder="默认值"
size="small"
:disabled="
value.type === 'object' || value.type === 'array' || value.disabled
"
>
<i
slot="append"
class="el-icon-edit"
@click="handleAction({ eventType: 'show-edit', field: 'default' })"
></i>
</el-input>
</el-col>
<el-col :span="isMock ? 4 : 5" class="col-item col-item-desc">
<el-input
v-model="value.description"
:disabled="value.disabled"
size="small"
placeholder="备注"
>
<i
slot="append"
class="el-icon-edit"
@click="
handleAction({ eventType: 'show-edit', field: 'description' })
"
></i>
</el-input>
</el-col>
<el-col :span="isMock ? 2 : 3" class="col-item col-item-setting">
<span
class="adv-set"
@click="
handleAction({ eventType: 'setting', schemaType: value.type })
"
>
<el-tooltip placement="top" content="高级设置">
<i class="el-icon-setting"></i>
</el-tooltip>
</span>
<span
class="delete-item"
:class="{ hidden: value.disabled }"
@click="handleAction({ eventType: 'delete-field' })"
>
<i class="el-icon-close close"></i>
</span>
<DropPlus
v-if="value.type === 'object'"
:prefix="prefix"
:name="name"
@add-field="handleAction"
/>
<span
v-if="value.type !== 'object'"
@click="handleAction({ eventType: 'add-field', isChild: false })"
>
<el-tooltip placement="top" content="添加兄弟节点">
<i class="el-icon-plus plus"></i>
</el-tooltip>
</span>
</el-col>
</el-row>
<div class="option-formStyle">
<!-- {mapping(prefixArray, value, showEdit, showAdv)} -->
<template v-if="value.type === 'array'">
<schema-array
:prefix="prefixArray"
:data="value"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
</template>
<template v-if="value.type === 'object' && showIcon">
<schema-object
:prefix="nameArray"
:data="value"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
</template>
</div>
</div>
</template>
<script>
import isUndefined from 'lodash/isUndefined'
import MockSelect from '../mock'
import DropPlus from './DropPlus'
import SchemaObject from './SchemaObject'
import SchemaArray from './SchemaArray'
import { SCHEMA_TYPE } from '../utils'
export default {
name: 'SchemaItem',
components: {
MockSelect,
DropPlus,
'schema-array': SchemaArray,
'schema-object': SchemaObject,
},
props: {
isMock: {
type: Boolean,
default: true,
},
showTitle: {
type: Boolean,
default: false,
},
showDefaultValue: { type: Boolean, default: false },
editorId: {
type: String,
default: 'editor_id',
},
name: {
type: String,
default: '',
},
prefix: {
type: Array,
default: () => [],
},
data: {
type: Object,
default: () => {},
},
},
data() {
return {
showIcon: true,
tagPaddingLeftStyle: {},
schemaTypes: SCHEMA_TYPE,
value: this.data.properties[this.name],
}
},
computed: {
nameArray() {
const prefixArray = [].concat(this.prefix, this.name)
return [].concat(prefixArray, 'properties')
},
prefixArray() {
return [].concat(this.prefix, this.name)
// return [].concat(this.prefix, 'items')
},
},
beforeMount() {
const length = this.prefix.filter((name) => name !== 'properties').length
this.tagPaddingLeftStyle = {
paddingLeft: `${20 * (length + 1)}px`,
}
},
methods: {
isUndefined() {
return isUndefined
},
handleClickIcon() {
this.showIcon = !this.showIcon
},
handleAction(options) {
const { prefix, name } = this
this.$jsEditorEvent.emit(`schema-update-${this.editorId}`, {
eventType: 'add-field',
prefix,
name,
...options,
})
},
handleNameChange(e) {
this.handleAction({
eventType: 'update-field-name',
value: e.target.value,
})
},
handleEnableRequire(e) {
const { prefix, name } = this
this.$jsEditorEvent.emit(`schema-update-${this.editorId}`, {
eventType: 'toggle-required',
prefix,
name,
required: e,
})
},
handleChangeMock() {},
handleChangeType(value) {
this.handleAction({ eventType: 'schema-type', value })
},
},
}
</script>

View File

@ -0,0 +1,54 @@
<template>
<div class="object-style">
<schema-item
v-for="(name,index) in propertyKeys"
:key="index"
:data="data"
:name="name"
:prefix="prefix"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
></schema-item>
</div>
</template>
<script>
export default {
name: 'SchemaObject',
components: { 'schema-item': () => import('./SchemaItem.vue') },
props: {
prefix: {
type: Array,
default: () => []
},
data: {
type: Object,
default: () => {}
},
isMock: { type: Boolean, default: false },
showTitle: {
type: Boolean,
default: false
},
showDefaultValue: { type: Boolean, default: false },
editorId: {
type: String,
default: 'editor_id'
}
},
data() {
return {
tagPaddingLeftStyle: {},
items: this.data.items
}
},
computed: {
propertyKeys() {
return Object.keys(this.data.properties)
}
},
methods: {}
}
</script>

View File

@ -0,0 +1,65 @@
<template>
<div class="schema-content" v-bind="$attrs">
<template v-if="data.type==='array'">
<schema-array
:prefix="name"
:data="data"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
</template>
<template v-if="data.type==='object'">
<schema-object
:prefix="nameArray"
:data="data"
:is-mock="isMock"
:show-title="showTitle"
:show-default-value="showDefaultValue"
:editor-id="editorId"
/>
</template>
</div>
</template>
<script>
import SchemaObject from './SchemaObject'
import SchemaArray from './SchemaArray'
export default {
name: 'SchemaJson',
components: {
'schema-array': SchemaArray,
'schema-object': SchemaObject
},
inheritAttrs: false,
props: {
data: {
type: Object,
default: () => {
return []
}
},
isMock: { type: Boolean, default: false },
showTitle: {
type: Boolean,
default: false
},
showDefaultValue: { type: Boolean, default: false },
editorId: {
type: String,
default: 'editor_id'
}
},
data() {
return {
name: []
}
},
computed: {
nameArray() {
return [].concat(this.name, 'properties')
}
},
methods: {}
}
</script>

View File

@ -0,0 +1,200 @@
export const JSONPATH_JOIN_CHAR = '.';
export const lang = 'zh_CN';
export const format = [
{name: 'date-time'},
{name: 'date'},
{name: 'email'},
{name: 'hostname'},
{name: 'ipv4'},
{name: 'ipv6'},
{name: 'uri'}
];
export const SCHEMA_TYPE = [
'string',
'number',
'array',
'object',
'boolean',
'integer'
];
export const defaultInitSchemaData = {
type: 'object',
title: 'title',
properties: {}
};
export const defaultSchema = {
string: {
type: 'string'
},
number: {
type: 'number'
},
array: {
type: 'array',
items: {
type: 'string'
}
},
object: {
type: 'object',
properties: {}
},
boolean: {
type: 'boolean'
},
integer: {
type: 'integer'
}
};
// 防抖函数,减少高频触发的函数执行的频率
// 请在 constructor 里使用:
// this.func = debounce(this.func, 400);
export const debounce = (func, wait) => {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(func, wait);
};
};
export const getData = (state, keys) => {
let curState = state;
for (let i = 0; i < keys.length; i++) {
curState = curState[keys[i]];
}
return curState;
};
export const setData = function (state, keys, value) {
let curState = state;
for (let i = 0; i < keys.length - 1; i++) {
curState = curState[keys[i]];
}
curState[keys[keys.length - 1]] = value;
};
export const deleteData = function (state, keys) {
let curState = state;
for (let i = 0; i < keys.length - 1; i++) {
curState = curState[keys[i]];
}
delete curState[keys[keys.length - 1]];
};
export const getParentKeys = function (keys) {
if (keys.length === 1) return [];
const arr = [].concat(keys);
arr.splice(keys.length - 1, 1);
return arr;
};
export const clearSomeFields = function (keys, data) {
const newData = Object.assign({}, data);
keys.forEach(key => {
delete newData[key];
});
return newData;
};
function getFieldstitle(data) {
const requiredtitle = [];
Object.keys(data).map(title => {
requiredtitle.push(title);
});
return requiredtitle;
}
export function handleSchemaRequired(schema, checked) {
if (schema.type === 'object') {
const requiredtitle = getFieldstitle(schema.properties);
// schema.required = checked ? [].concat(requiredtitle) : [];
if (checked) {
schema.required = [].concat(requiredtitle);
} else {
delete schema.required;
}
handleObject(schema.properties, checked);
} else if (schema.type === 'array') {
handleSchemaRequired(schema.items, checked);
} else {
return schema;
}
}
function handleObject(properties, checked) {
for (var key in properties) {
if (properties[key].type === 'array' || properties[key].type === 'object') {
handleSchemaRequired(properties[key], checked);
}
}
}
export function cloneObject(obj) {
if (typeof obj === 'object') {
if (Array.isArray(obj)) {
var newArr = [];
obj.forEach(function (item, index) {
newArr[index] = cloneObject(item);
});
return newArr;
} else {
var newObj = {};
for (var key in obj) {
newObj[key] = cloneObject(obj[key]);
}
return newObj;
}
} else {
return obj;
}
}
export const uuid = () => {
return Math.random()
.toString(16)
.substr(2, 5);
};
export const log = (...args) => {
};
/**
* val值不为空字符nullundefined
*/
export const isNotNil = val => {
const arr = [undefined, null, ''];
return !arr.includes(val);
};
/**
* form表单值校验是否为空有值为空则返回true值都正确则返回false
*/
export const isFormValid = obj => {
if (typeof obj !== 'object') return true;
const keys = Object.keys(obj);
return keys.some(key => {
return !isNotNil(obj[key]);
});
};
/**
* 只返回有值得属性新对象
* @param {Object} formData 表单对象
*/
export const getValidFormVal = formData => {
const obj = {};
const keys = Object.keys(formData);
keys.forEach(key => {
if (isNotNil(formData[key])) {
obj[key] = formData[key];
}
});
return obj;
};

View File

@ -1,324 +0,0 @@
<template>
<div v-loading="loading">
<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="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>
<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: {MsApiVariableAdvance},
data() {
return {
loading: false,
activeName: "apiTemplate",
parameters: [],
currentItem: null,
tableData: [{
id: "root",
parent: null,
name: 'root',
required: true,
type: 'object',
value: 'value',
describe: 'describe',
editor: false,
children: [],
}]
,
tabClickProperty: "",
tabClickIndex: "",
typeData: [
{id: 'string', label: 'string'},
{id: 'number', label: 'number'},
{id: 'array', label: 'array'},
{id: 'object', label: 'object'},
{id: 'boolean', label: 'boolean'},
{id: 'integer', label: 'integer'}
]
}
},
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 '':
this.tabClickIndex = row.id
this.tabClickProperty = column.property
break;
case 'describe':
this.tabClickIndex = row.id
this.tabClickProperty = column.property
break;
default:
return
}
},
inputBlur() {
this.tabClickIndex = null;
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(() => {
this.loading = false
})
},
setting() {
},
add(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;
}
let parentRow = {};
const index = this.tableData.findIndex(d => d.id != undefined && row.parent != undefined && d.id === row.parent);
if (index != -1) {
parentRow = this.tableData[index];
} else {
for (let i in this.tableData) {
if (this.tableData[i].children) {
parentRow = this.recursiveFind(this.tableData[i].children, row);
}
}
}
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);
}
if (arr[i].children != undefined && arr[i].children.length > 0) {
this.recursiveRemove(arr[i].children, row);
}
}
},
deleteRow(row) {
const index = this.tableData.findIndex(d => d.id != undefined && row.id != undefined && d.id === row.id)
if (index == -1) {
this.tableData.forEach(item => {
if (item.children) {
this.recursiveRemove(item.children, row);
}
})
} 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) {
}
}
}
</script>
<style scoped>
.name-input >>> .el-input__inner {
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>

View File

@ -19,6 +19,9 @@ import '../common/css/main.css';
import CKEditor from '@ckeditor/ckeditor5-vue';
import VueFab from 'vue-float-action-button'
import {horizontalDrag} from "../common/js/directive";
import JsonSchemaEditor from './components/common/json-schema/index';
Vue.use(JsonSchemaEditor);
Vue.config.productionTip = false;
Vue.use(icon);