fix(接口自动化): JSON-SCHEMA 编辑器添加

This commit is contained in:
fit2-zhao 2020-12-30 16:48:16 +08:00
parent 4b975918e1
commit 0a85155932
44 changed files with 125161 additions and 2523 deletions

View File

@ -24,6 +24,8 @@
"html2canvas": "^1.0.0-rc.7",
"js-base64": "^3.4.4",
"json-bigint": "^1.0.0",
"json-schema-faker": "^0.5.0-rcv.32",
"json5": "^2.1.3",
"jsoneditor": "^9.1.2",
"jspdf": "^2.1.1",
"md5": "^2.3.0",
@ -41,11 +43,7 @@
"vuedraggable": "^2.23.2",
"vuex": "^3.1.2",
"xml-js": "^1.6.11",
"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"
"yan-progress": "^1.0.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.1.0",
@ -56,10 +54,7 @@
"eslint-plugin-vue": "^5.0.0",
"file-writer": "^1.0.2",
"vue-template-compiler": "^2.6.10",
"vue2-ace-editor": "0.0.15",
"node-sass": "^4.14.1",
"sass-loader": "^9.0.2",
"script-loader": "^0.7.2"
"vue2-ace-editor": "0.0.15"
},
"eslintConfig": {
"root": true,

View File

@ -2,8 +2,8 @@
<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 style="min-height: 400px">
<json-schema-editor class="schema" :value.sync="schema" lang="zh_CN" custom/>
</div>
</el-tab-pane>
<el-tab-pane label="预览" name="preview">
@ -18,19 +18,19 @@
<script>
import {schemaToJson} from './common';
import json5 from 'json5';
export default {
name: 'App',
components: {},
data() {
return {
schema: {
type: 'object',
title: 'title',
properties: {
field_1: {
type: 'string'
}
schema:
{
"root": {
"type": "object",
"mock": {"mock": ""},
"properties": {},
}
},
preview: null,
@ -40,9 +40,7 @@
methods: {
handleClick() {
if (this.activeName === 'preview') {
console.log(this.schema)
this.preview = schemaToJson(this.schema);
this.preview = schemaToJson(json5.parse(JSON.stringify(this.schema)));
}
}
}

View File

@ -11,9 +11,7 @@ jsf.extend('mock', function () {
const defaultOptions = {
failOnInvalidTypes: false,
failOnInvalidFormat: false,
alwaysFakeOptionals: true,
useDefaultValue: true
failOnInvalidFormat: false
};
export const schemaToJson = (schema, options = {}) => {

View File

@ -1,82 +0,0 @@
<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

@ -1,65 +0,0 @@
<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

@ -1,83 +0,0 @@
<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

@ -1,164 +0,0 @@
<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

@ -1,78 +0,0 @@
<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

@ -1,43 +0,0 @@
<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

@ -1,153 +0,0 @@
<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

@ -1,17 +0,0 @@
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

@ -1,48 +0,0 @@
/**
* @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

@ -1,17 +0,0 @@
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

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

View File

@ -1,90 +0,0 @@
<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

@ -1,617 +0,0 @@
<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

@ -1,149 +0,0 @@
.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,10 @@
<meta charset="utf-8">
<title>json-schema-editor-vue demo</title>
<script src="./json-schema-editor-vue.umd.js"></script>
<link rel="stylesheet" href="./json-schema-editor-vue.css">
<script>
console.log(json-schema-editor-vue)
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,25 @@
import JsonSchemaEditor from './json-schema-editor/index'
const components = [
JsonSchemaEditor
]
// 定义 install 方法
const install = function (Vue) {
if (install.installed) return
install.installed = true
// 遍历并注册全局组件
components.map(component => {
Vue.component(component.name, component)
})
}
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export default {
// 导出的对象必须具备一个 install 方法
install,
// 组件列表
...components
}

View File

@ -0,0 +1,90 @@
const langs = {
en_US: {
'title': 'Title',
'import_json': 'Import JSON',
'base_setting': 'Base Setting',
'all_setting': 'Source Code',
'default': 'Default',
'description':'Description',
"adv_setting": "Advanced Settings",
"add_child_node": "Add child node",
'add_sibling_node': 'Add sibling nodes',
'add_node':'Add sibling/child nodes',
'remove_node': 'Remove node',
'child_node': 'Child node',
'sibling_node':'Sibling node',
'ok':'OK',
'cancel':'Cancel',
'minLength':'Min length',
'maxLength': 'Max length',
'pattern':'MUST be a valid regular expression.',
'exclusiveMinimum': 'Value strictly less than',
'exclusiveMaximum': 'Value strictly more than',
'minimum': 'Min',
'maximum': 'Max',
'uniqueItems': 'Unique Items',
'minItems':'MinItems',
'maxItems': 'MaxItems',
'minProperties':'MinProperties',
'maxProperties': 'MaxProperties',
'checked_all': 'Checked All',
'valid_json': 'Not valid json',
'enum': 'Enum',
'enum_msg': 'One value per line',
'enum_desc': 'desc',
'enum_desc_msg': 'enum description',
'required': 'Required',
'mock': 'mock',
'mockLink': 'Help',
'format': 'Format',
'nothing': 'Nothing',
'preview': 'Preview',
'add_custom': 'Add Custom Prop',
},
zh_CN: {
'title': '标题',
'import_json': '导入 json',
'base_setting': '基础设置',
'all_setting': '编辑源码',
'default': '默认值',
'description':'描述',
'adv_setting': '高级设置',
"add_child_node": "添加子节点",
'add_sibling_node': '添加兄弟节点',
'add_node':'添加兄弟/子节点',
'remove_node': '删除节点',
'child_node': '子节点',
'sibling_node':'兄弟节点',
'ok':'确定',
'cancel':'取消',
'minLength':'最小长度',
'maxLength': '最大长度',
'pattern': '用正则表达式约束字符串',
'exclusiveMinimum': '开启后,数据必须大于最小值',
'exclusiveMaximum': '开启后,数据必须小于最大值',
'minimum': '最小值',
'maximum': '最大值',
'uniqueItems': '开启后,每个元素都不相同',
'minItems':'最小元素个数',
'maxItems': '最大元素个数',
'minProperties':'最小元素个数',
'maxProperties': '最大元素个数',
'checked_all': '全选',
'valid_json': '不是合法的json字符串',
'enum': '枚举',
'enum_msg': '每行只能写一个值',
'enum_desc': '备注',
'enum_desc_msg': '备注描述信息',
'required': '是否必须',
'mock': 'mock',
'mockLink': '查看文档',
'format': '格式化',
'nothing': '无',
'preview': '预览',
'add_custom': '添加自定义属性'
}
}
export default (lang) => {
return langs[lang]
}

View File

@ -0,0 +1,7 @@
import JsonSchemaEditor from './main.vue'
JsonSchemaEditor.install = function (Vue) {
Vue.component(JsonSchemaEditor.name, JsonSchemaEditor)
}
export default JsonSchemaEditor

View File

@ -0,0 +1,391 @@
<template>
<div class="json-schema-editor">
<el-row class="row" :gutter="10">
<el-col :span="12" class="ant-col-name">
<div :style="{marginLeft:`${10*deep}px`}" class="ant-col-name-c">
<span v-if="pickValue.type==='object'" :class="hidden? 'el-tree-node__expand-icon el-icon-caret-right':
'expanded el-tree-node__expand-icon el-icon-caret-right'" @click="hidden = !hidden"/>
<span v-else style="width:10px;display:inline-block"></span>
<el-input :disabled="disabled || root" :value="pickKey" @blur="onInputName" size="small"/>
</div>
<el-tooltip v-if="root" content="全选">
<input type="checkbox" :disabled="!isObject && !isArray" class="ant-col-name-required" @change="onRootCheck"/>
</el-tooltip>
<el-tooltip v-else content="是否必填">
<input type="checkbox" :disabled="isItem" :checked="checked" class="ant-col-name-required" @change="onCheck"/>
</el-tooltip>
</el-col>
<el-col :span="4">
<el-select v-model="pickValue.type" :disabled="disabledType" class="ant-col-type" @change="onChangeType" size="small">
<el-option :key="t" :value="t" :label="t" v-for="t in TYPE_NAME"/>
</el-select>
</el-col>
<el-col :span="8">
<ms-mock :schema="pickValue.mock"/>
</el-col>
<el-col>
<el-input v-model="pickValue.description" class="ant-col-title" :placeholder="local['description']" size="small"/>
</el-col>
<el-col :span="4" class="col-item-setting">
<el-tooltip class="item" effect="dark" content="高级设置" placement="top">
<i class="el-icon-setting" @click="onSetting"/>
</el-tooltip>
<el-tooltip v-if="isObject" content="添加子节点" placement="top">
<i class="el-icon-plus" @click="addChild" style="margin-left: 10px"/>
</el-tooltip>
<el-tooltip v-if="!root && !isItem" content="删除节点" placement="top">
<i class="el-icon-close" @click="removeNode" style="margin-left: 10px"/>
</el-tooltip>
</el-col>
</el-row>
<template v-if="!hidden&&pickValue.properties && !isArray">
<json-schema-editor v-for="(item,key,index) in pickValue.properties" :value="{[key]:item}" :parent="pickValue" :key="index" :deep="deep+1" :root="false" class="children" :lang="lang" :custom="custom"/>
</template>
<template v-if="isArray">
<json-schema-editor :value="{items:pickValue.items}" :deep="deep+1" disabled isItem :root="false" class="children" :lang="lang" :custom="custom"/>
</template>
<!-- 高级设置-->
<el-dialog :close-on-click-modal="false" :title="local['adv_setting']" :visible.sync="modalVisible" width="60%" :destroy-on-close="true"
@close="handleClose">
<!--<el-dialog v-model="modalVisible" :title="local['adv_setting']" :maskClosable="false" :okText="local['ok']" :cancelText="local['cancel']" width="800px" @ok="handleOk" dialogClass="json-schema-editor-advanced-modal">-->
<h3 v-text="local['base_setting']">基础设置</h3>
<el-form v-model="advancedValue" class="ant-advanced-search-form">
<el-row :gutter="6">
<el-col :span="8" v-for="(item,key) in advancedValue" :key="key">
<el-form-item>
<el-input-number v-model="advancedValue[key]" v-if="advancedAttr[key].type === 'integer'" style="width:100%" :placeholder="key" size="small"/>
<el-input-number v-model="advancedValue[key]" v-else-if="advancedAttr[key].type === 'number'" style="width:100%" :placeholder="key" size="small"/>
<span v-else-if="advancedAttr[key].type === 'boolean'" style="display:inline-block;width:100%">
<el-switch :active-text="local[key]" v-model="advancedValue[key]"/>
</span>
<el-select v-else-if="advancedAttr[key].type === 'array'" v-model="advancedValue[key]" style="width:100%" size="small">
<el-option value="" :label="local['nothing']"></el-option>
<el-option :key="t" :value="t" :label="t" v-for="t in advancedAttr[key].enums"/>
</el-select>
<el-input v-model="advancedValue[key]" v-else style="width:100%" :placeholder="key" size="small"/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!--<h3 v-text="local['add_custom']" v-show="custom">添加自定义属性</h3>
<el-form class="ant-advanced-search-form" v-show="custom">
<el-row :gutter="6">
<el-col :span="8" v-for="item in customProps" :key="item.key">
<el-form-item :label="item.key">
<el-input v-model="item.value" style="width:calc(100% - 30px)" size="small"/>
<el-button icon="close" type="link" @click="customProps.splice(customProps.indexOf(item),1)" size="small"/>
</el-form-item>
</el-col>
<el-col :span="8" v-show="addProp.key != undefined">
<el-form-item>
<el-input slot="label" v-model="addProp.key" size="small"/>
<el-input v-model="addProp.value" size="small"/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item>
<el-button icon="check" type="link" @click="confirmAddCustomNode" v-if="customing"/>
<el-tooltip content="local['add_custom']" v-else>
<el-button icon="el-icon-plus" type="link" @click="addCustomNode"/>
</el-tooltip>
</el-form-item>
</el-col>
</el-row>
</el-form>-->
<h3 v-text="local['preview']">预览</h3>
<pre style="width:100%">{{completeNodeValue}}</pre>
<span slot="footer" class="dialog-footer">
<ms-dialog-footer
@cancel="modalVisible = false"
@confirm="handleOk"/>
</span>
</el-dialog>
</div>
</template>
<script>
import {isNull} from './util'
import {TYPE_NAME, TYPE} from './type/type'
import MsMock from './mock/index'
import MsDialogFooter from '../../../../../common/components/MsDialogFooter'
import LocalProvider from './LocalProvider'
export default {
name: 'JsonSchemaEditor',
components: {MsMock, MsDialogFooter},
props: {
value: {
type: Object,
required: true
},
disabled: { //namename,name
type: Boolean,
default: false
},
disabledType: { //
type: Boolean,
default: false
},
isItem: { //
type: Boolean,
default: false
},
deep: { // deep=0
type: Number,
default: 0
},
root: { //root
type: Boolean,
default: true
},
parent: { //
type: Object,
default: null
},
custom: { //enable custom properties
type: Boolean,
default: false
},
lang: { // i18n language
type: String,
default: 'zh_CN'
}
},
computed: {
pickValue() {
return Object.values(this.value)[0]
},
pickKey() {
return Object.keys(this.value)[0]
},
isObject() {
return this.pickValue.type === 'object'
},
isArray() {
return this.pickValue.type === 'array'
},
checked() {
return this.parent && this.parent.required && this.parent.required.indexOf(this.pickKey) >= 0
},
advanced() {
return TYPE[this.pickValue.type]
},
advancedAttr() {
return TYPE[this.pickValue.type].attr
},
advancedNotEmptyValue() {
const jsonNode = Object.assign({}, this.advancedValue);
for (let key in jsonNode) {
isNull(jsonNode[key]) && delete jsonNode[key]
}
return jsonNode
},
completeNodeValue() {
const t = {}
for (const item of this.customProps) {
t[item.key] = item.value
}
return Object.assign({}, this.pickValue, this.advancedNotEmptyValue, t)
}
},
data() {
return {
TYPE_NAME,
hidden: false,
countAdd: 1,
modalVisible: false,
advancedValue: {},
addProp: {},//
customProps: [],
customing: false,
local: LocalProvider(this.lang)
}
},
methods: {
onInputName(e) {
const val = e.target.value
const p = {};
for (let key in this.parent.properties) {
if (key != this.pickKey) {
p[key] = this.parent.properties[key]
} else {
p[val] = this.parent.properties[key]
delete this.parent.properties[key]
}
}
this.$set(this.parent, 'properties', p)
},
onChangeType() {
this.$delete(this.pickValue, 'properties')
this.$delete(this.pickValue, 'items')
this.$delete(this.pickValue, 'required')
if (this.isArray) {
this.$set(this.pickValue, 'items', {type: 'string',mock: {mock: ""}})
}
},
onCheck(e) {
this._checked(e.target.checked, this.parent)
},
onRootCheck(e) {
const checked = e.target.checked
this._deepCheck(checked, this.pickValue)
},
_deepCheck(checked, node) {
if (node.type === 'object' && node.properties) {
checked ? this.$set(node, 'required', Object.keys(node.properties)) : this.$delete(node, 'required')
Object.keys(node.properties).forEach(key => this._deepCheck(checked, node.properties[key]))
} else if (node.type === 'array' && node.items.type === 'object') {
checked ? this.$set(node.items, 'required', Object.keys(node.items.properties)) : this.$delete(node.items, 'required')
Object.keys(node.items.properties).forEach(key => this._deepCheck(checked, node.items.properties[key]))
}
},
_checked(checked, parent) {
let required = parent.required
if (checked) {
required || this.$set(this.parent, 'required', [])
required = this.parent.required
required.indexOf(this.pickKey) === -1 && required.push(this.pickKey)
} else {
const pos = required.indexOf(this.pickKey)
pos >= 0 && required.splice(pos, 1)
}
required.length === 0 && this.$delete(parent, 'required')
},
addChild() {
const name = this._joinName()
const type = 'string'
const node = this.pickValue
node.properties || this.$set(node, 'properties', {})
const props = node.properties
this.$set(props, name, {type: type, mock: {mock: ""}})
},
addCustomNode() {
this.$set(this.addProp, 'key', this._joinName())
this.$set(this.addProp, 'value', '')
this.customing = true
},
confirmAddCustomNode() {
this.customProps.push(this.addProp)
this.addProp = {}
this.customing = false
},
removeNode() {
const {properties, required} = this.parent
this.$delete(properties, this.pickKey)
if (required) {
const pos = required.indexOf(this.pickKey)
pos >= 0 && required.splice(pos, 1)
required.length === 0 && this.$delete(this.parent, 'required')
}
},
_joinName() {
return `feild_${this.deep}_${this.countAdd++}`
},
onSetting() {
this.modalVisible = true;
this.advancedValue = this.advanced.value
for (const k in this.advancedValue) {
if (this.pickValue[k]) this.advancedValue[k] = this.pickValue[k]
}
},
handleClose() {
this.modalVisible = false;
},
handleOk() {
this.modalVisible = false
for (const key in this.advancedValue) {
if (isNull(this.advancedValue[key])) {
this.$delete(this.pickValue, key)
} else {
this.$set(this.pickValue, key, this.advancedValue[key])
}
}
for (const item of this.customProps) {
this.$set(this.pickValue, item.key, item.value)
}
}
}
}
</script>
<style scoped>
.json-schema-editor .row {
display: flex;
margin: 12px;
}
.json-schema-editor .row .ant-col-name {
display: flex;
align-items: center;
}
.json-schema-editor .row .ant-col-name .ant-col-name-c {
display: flex;
align-items: center;
}
.json-schema-editor .row .ant-col-name .ant-col-name-required {
flex: 0 0 24px;
text-align: center;
}
.json-schema-editor .row .ant-col-type {
min-width: 100px;
width: 100%;
}
.json-schema-editor .row .ant-col-setting {
display: inline-block;
}
.json-schema-editor .row .setting-icon {
color: rgba(0, 0, 0, 0.45);
border: none;
}
.json-schema-editor .row .plus-icon {
border: none;
}
.json-schema-editor .row .close-icon {
color: #888;
border: none;
}
</style>
<style>
.json-schema-editor-advanced-modal {
color: rgba(0, 0, 0, 0.65);
min-width: 600px;
}
.json-schema-editor-advanced-modal pre {
font-family: monospace;
height: 100%;
overflow-y: auto;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 4px;
padding: 12px;
width: 50%;
}
.json-schema-editor-advanced-modal h3 {
display: block;
border-left: 3px solid #1890ff;
padding: 0 8px;
}
.json-schema-editor-advanced-modal .ant-advanced-search-form {
display: flex;
}
.json-schema-editor-advanced-modal .ant-advanced-search-form .ant-form-item .ant-form-item-control-wrapper {
flex: 1;
}
.col-item-setting {
padding-top: 8px;
cursor: pointer;
}
</style>

View File

@ -31,10 +31,6 @@
default: () => {
}
},
mock: {
type: Array,
default: () => []
}
},
data() {
return {
@ -42,6 +38,9 @@
}
},
created() {
if (!this.schema.mock) {
this.schema.mock = "";
}
},
mounted() {
},

View File

@ -0,0 +1,26 @@
const value = {
description: null,
minItems:null,
maxItems:null,
uniqueItems:false
}
const attr = {
description: {
name: '描述',
type: 'string'
},
maxItems:{
name: '最大元素个数',
type: 'integer'
},
minItems:{
name: '最小元素个数',
type: 'integer'
},
uniqueItems:{
name:'元素不可重复',
type: 'boolean'
}
}
const wrapper = {value, attr}
export default wrapper

View File

@ -0,0 +1,11 @@
const value = {
description: null
}
const attr = {
description: {
name: '描述',
type: 'string'
}
}
const wrapper = {value, attr}
export default wrapper

View File

@ -0,0 +1,31 @@
const value = {
description: null,
maximum: null,
minimum: null,
exclusiveMaximum:null,
exclusiveMinimum:null
}
const attr = {
description: {
name: '描述',
type: 'string',
},
maximum:{
name:'最大值',
type:'integer'
},
minimum:{
name:'最小值',
type:'integer'
},
exclusiveMaximum:{
name:'不包含最大值',
type:'boolean'
},
exclusiveMinimum:{
name:'不包含最小值',
type:'boolean'
}
}
const wrapper = {value, attr}
export default wrapper

View File

@ -0,0 +1,31 @@
const value = {
description: null,
maximum: null,
minimum: null,
exclusiveMaximum:null,
exclusiveMinimum:null
}
const attr = {
description: {
name: '描述',
type: 'string',
},
maximum:{
name:'最大值',
type:'number'
},
minimum:{
name:'最小值',
type:'number'
},
exclusiveMaximum:{
name:'不包含最大值',
type:'boolean'
},
exclusiveMinimum:{
name:'不包含最小值',
type:'boolean'
}
}
const wrapper = {value, attr}
export default wrapper

View File

@ -0,0 +1,21 @@
const value = {
description: null,
maxProperties: null,
minProperties: null
}
const attr = {
description: {
name: '描述',
type: 'string',
},
maxProperties:{
name:'最大元素个数',
type:'integer'
},
minProperties:{
name:'最小元素个数',
type:'integer'
}
}
const wrapper = {value, attr}
export default wrapper

View File

@ -0,0 +1,32 @@
const value = {
description: null,
maxLength: null,
minLength: null,
pattern: null,
format:null
}
const attr = {
description: {
name: '描述',
type: 'string',
},
maxLength:{
name:'最大字符数',
type:'integer'
},
minLength:{
name:'最小字符数',
type:'integer'
},
pattern: {
name: '正则表达式',
type:'string'
},
format: {
name:'格式',
type:'array',
enums:['date','date-time','email','hostname','ipv4','ipv6','uri']
}
}
const wrapper = {value, attr}
export default wrapper

View File

@ -0,0 +1,17 @@
import _object from './object'
import _string from './string'
import _array from './array'
import _boolean from './boolean'
import _integer from './integer'
import _number from './number'
const TYPE_NAME = ['string', 'number', 'integer','object', 'array', 'boolean']
const TYPE = {
'object': _object,
'string': _string,
'array': _array,
'boolean': _boolean,
'integer': _integer,
'number': _number
}
export {TYPE ,TYPE_NAME}

View File

@ -0,0 +1,24 @@
export function clearAttr(obj) {
for(let key in obj){
delete obj[key]
}
}
/**
* 快速拷贝两个对象的属性值
* @param {*} source
* @param {*} target
*/
export function copyAttr(source, target){
Object.keys(target).forEach(key=>{target[key]=source[key]})
}
export function isNull(ele){
if(typeof ele==='undefined'){
return true;
}else if(ele==null){
return true;
}else if(ele==''){
return true;
}
return false;
}

View File

@ -1,45 +0,0 @@
<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

@ -1,224 +0,0 @@
<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

@ -1,290 +0,0 @@
<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

@ -1,54 +0,0 @@
<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

@ -1,65 +0,0 @@
<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

@ -1,200 +0,0 @@
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

@ -19,10 +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';
import JsonSchemaEditor from './components/common/json-schema/ot/packages/index';
Vue.use(JsonSchemaEditor);
Vue.config.productionTip = false;
Vue.use(icon);
Vue.use(ElementUI, {