mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-12-04 21:19:52 +08:00
fix(接口自动化): 增加json 列表编辑器
This commit is contained in:
parent
68e58a9b1b
commit
22fee64092
@ -134,4 +134,5 @@ public class ApiDefinitionController {
|
||||
public void testPlanRelevance(@RequestBody ApiCaseRelevanceRequest request) {
|
||||
apiDefinitionService.testPlanRelevance(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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";
|
||||
|
@ -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>
|
@ -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;
|
||||
};
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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
|
||||
};
|
48
frontend/src/business/components/common/json-schema/event.js
Normal file
48
frontend/src/business/components/common/json-schema/event.js
Normal 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;
|
17
frontend/src/business/components/common/json-schema/index.js
Normal file
17
frontend/src/business/components/common/json-schema/index.js
Normal 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;
|
@ -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;
|
@ -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>
|
@ -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>
|
@ -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;
|
||||
}
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
200
frontend/src/business/components/common/json-schema/utils.js
Normal file
200
frontend/src/business/components/common/json-schema/utils.js
Normal 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值不为空字符,null,undefined
|
||||
*/
|
||||
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;
|
||||
};
|
@ -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>
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user