src/components/json-schema/index.vue
<!-- -->
<template>
<div>
<div class="tree-part-test flex">
<pre>{{ showData }}</pre>
<el-tree :data="treeData" node-key="id" default-expand-all :expand-on-click-node="false">
<template #default="{ node, data }">
<span class="doc-tree-node mb6">
<el-input
v-model="data.label"
placeholder="请输入内容"
class="mr6"
size="small"
:disabled="data.disabled"
style="width:180px"
></el-input>
<el-tooltip effect="dark" content="是否必须" placement="top" v-if="data.id != 1">
<el-checkbox v-model="data.required" style="margin-right:6px"></el-checkbox>
</el-tooltip>
<el-select
v-model="data.type"
class="mr6"
size="small"
:disabled="data.id === 1"
style="width:180px"
@change="val => handleSelect(val, data)"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
<el-select
v-model="data.format"
class="mr6"
size="small"
v-if="['integer', 'number'].includes(data.type)"
style="width:180px"
placeholder="format方式"
>
<el-option v-for="item in formatOptions[data.type]" :key="item" :label="item" :value="item">
</el-option>
</el-select>
<el-select
v-if="data.type === 'ref'"
v-model="data.$ref"
class="mr6"
size="small"
:disabled="data.id === 1"
style="width:180px"
>
<el-option v-for="item in definitionOptions" :key="item" :label="item" :value="item">
</el-option>
</el-select>
<el-input
v-else
v-model="data.description"
placeholder="请输入内容"
class="mr6"
size="small"
style="width:200px"
></el-input>
<span v-if="data.type === 'object'" class="classNameWrapper">
<el-input
v-model="data.title"
placeholder="请输入类名"
:class="['mr6', !data.title ? 'error-required' : '']"
size="small"
style="width:200px"
/>
<div v-if="!data.title" class="tips">类名不能为空</div>
</span>
<span v-if="['object', 'array'].includes(data.type) && node.level == 2">
<span class="text-tips">引用:</span>
<el-switch v-model="data.defination" class="mr6"></el-switch>
</span>
<i
v-if="data.type === 'object'"
class="el-icon-plus mr12 ml6"
style="font-size:18px;color:#36f;font-weight:bolder"
@click="append(data)"
></i>
<i
v-if="data.id !== 1"
class="el-icon-delete"
@click="remove(node, data)"
style="font-size:18px"
></i>
<el-button v-if="node.level == 1" type="primary" size="mini" @click="dialogVisible = true">
导入
</el-button>
</span>
</template>
</el-tree>
</div>
<el-dialog title="提示" v-model="dialogVisible" width="30%" append-to-body>
<!-- <el-input v-model="form.json" type="textarea" :rows="20"></el-input> -->
<Monaco v-model:value="form.json"></Monaco>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="handleImport()">确 定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import { onMounted, reactive, ref, watch } from "vue";
import { getUUID } from "@/utils";
import { useStore } from "vuex";
import Monaco from "@/components/online-editor";
const options = [
{ label: "string", value: "string" },
{ label: "number", value: "number" },
{ label: "integer", value: "integer" },
{ label: "object", value: "object" },
{ label: "array", value: "array" },
{ label: "boolean", value: "boolean" },
{ label: "ref", value: "ref" }
];
const formatOptions = {
integer: ["int32", "int64"],
number: ["double", "float"]
};
let number = 0;
let missingTitle = false;
export default {
components: { Monaco },
props: {
value: {
type: String,
default() {
return JSON.stringify([
{
label: "root",
id: 1,
disabled: true,
title: "",
type: "object",
description: "root",
children: []
}
]);
}
}
},
setup(props, { emit }) {
const store = useStore();
const definitionOptions = ref(new Set());
const dialogVisible = ref(false);
const show = ref(false);
const form = reactive({
json: ""
});
const showData = ref({});
const handleImport = () => {
const res = transform(transformByImportJson(form.json));
treeData.value = res;
dialogVisible.value = false;
setTimeout(() => {
show.value = true;
}, 1000);
};
const transformByImportJson = json => {
json = JSON.parse(json);
const process = (target, dataSource) => {
target.properties = {};
Object.keys(dataSource).forEach(key => {
const value = dataSource[key];
const type = typeof value;
console.log("value :>> ", value);
if (Array.isArray(value)) {
target.properties[key] = {
type: "array",
description: "",
items: {
type: "object",
properties: {}
}
};
console.log(target.properties[key].items, value[0]);
process(target.properties[key].items, value[0]);
} else {
target.properties[key] = {
type,
description: type === "object" ? "" : value
};
if (type === "object") {
process(target.properties[key], value);
}
}
});
};
const res = {
type: "object",
description: "",
title: ""
};
process(res, json);
console.log("res :>> ", res);
return JSON.stringify(res);
};
const transform = value => {
let data;
try {
data = JSON.parse(value);
} catch (error) {
data = {
type: "object",
description: "root",
title: "",
properties: {},
definitions: {}
};
}
//properties, definitions,
const { description, title } = data;
const res = [
{
label: "root",
id: 1,
disabled: true,
type: "object",
title: title,
description: description,
children: []
}
];
const process = (schemaObj, treeObj) => {
// console.log(11, schemaObj, treeObj);
const properties = schemaObj.properties;
const items = schemaObj.items;
const definitions = schemaObj.definitions;
const children = treeObj.children;
// console.log("items :>> ", items);
// if (!children) return;
if (items) {
const child = {
label: "items",
type: items.type,
description: items.description,
disabled: true,
$ref: items.$ref ? items.$ref.split("#/definitions/")[1] : undefined,
children: []
};
process(items, child);
children.push(child);
}
properties &&
Object.keys(properties).forEach(key => {
const item = properties[key];
const child = {
label: key,
type: item.type,
description: item.description,
title: item.title,
format: ["integer", "number"].includes(item.type) ? item.format : undefined,
required: item.required,
id: getUUID(),
children: [],
disabled: schemaObj.type === "array"
};
if (item.type === "ref") {
child.$ref = item.$ref.split("#/definitions/")[1];
}
process(item, child);
children.push(child);
});
definitions &&
Object.keys(definitions).forEach(key => {
const item = definitions[key];
const child = {
label: key,
type: item.type,
description: item.description,
title: item.title,
format: ["integer", "number"].includes(item.type) ? item.format : undefined,
required: item.required,
id: getUUID(),
defination: true,
children: []
};
process(item, child);
children.push(child);
});
};
process(data, res[0]);
return res;
};
const treeData = ref(transform(props.value));
const checkTitle = title => {
missingTitle = !title;
};
watch(
() => treeData,
treeData => {
const res = {
type: "object",
description: treeData.value[0].description,
title: treeData.value[0].title,
properties: {},
definitions: {}
};
checkTitle(treeData.value[0].title);
const process = (children, target, parent) => {
if (!children) return;
children.forEach(item => {
target.properties = target.properties || {};
// console.log("target :>> ", target);
const child = {
type: item.type,
description: item.description,
title: item.type === "object" ? item.title : undefined,
format: ["integer", "number"].includes(item.type) ? item.format : undefined,
required: item.required
};
if (item.type === "object") checkTitle(item.title);
if (target.type === "array") {
delete target.properties;
target[item.label] = {
...child,
$ref: item.type === "ref" ? `#/definitions/${item.$ref}` : undefined
};
} else {
target.properties[item.label] = {
...child,
$ref: item.type === "ref" ? `#/definitions/${item.$ref}` : undefined
};
}
if (parent.defination) {
const copy = res.properties[parent.label] || res.definitions[parent.label];
definitionOptions.value.add(parent.label);
res.definitions[parent.label] = copy;
delete res.properties[parent.label];
}
if (item.children) {
// console.log("target :>> ", target);
if (target.type === "array") {
process(item.children, target[item.label], item);
} else {
process(item.children, target.properties[item.label], item);
}
}
});
};
process(treeData.value[0].children, res, treeData.value[0]);
emit("update:value", JSON.stringify(res));
store.commit("api/SET_TITLEREQUIRED", missingTitle);
showData.value = res;
},
{
deep: true, // 深度监听的参数
immediate: true
}
);
onMounted(() => {});
const remove = (node, data) => {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex(d => d.id === data.id);
children.splice(index, 1);
};
const append = data => {
const newChild = {
id: getUUID(),
label: "field_" + number++,
type: "string",
description: "",
title: "",
children: []
};
if (!data.children) {
data.children = [];
}
data.children.push(newChild);
};
const handleSelect = (val, data) => {
if (val === "array") {
const newChild = { id: getUUID(), label: "items", type: "string", children: [], disabled: true };
if (!data.children) {
data.children = [];
}
data.children.push(newChild);
}
if (["integer", "number"].includes(val)) {
data.format = "";
}
};
return {
treeData,
remove,
options,
append,
handleSelect,
definitionOptions,
showData,
formatOptions,
dialogVisible,
handleImport,
form,
show
};
}
};
</script>
<style lang="scss">
.tree-part-test {
.doc-tree-node {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
padding-right: 8px;
}
.el-tree-node__content {
padding: 25px 0;
}
.el-tree-node__content {
height: 50px;
}
.el-tree-node__content:hover {
background-color: transparent;
}
pre {
width: 300px;
font-family: monospace;
margin-top: 0;
margin-bottom: 1em;
overflow: auto;
height: 100%;
overflow-y: auto;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 12px;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
.error-required .el-input__inner {
border-color: #f56c6c;
}
.classNameWrapper {
position: relative;
.tips {
color: #f56c6c;
position: absolute;
left: 0px;
bottom: -27px;
font-size: 12px;
}
}
}
</style>
使用
<JsonSchema v-model:value="form.schema" />
效果图
image.png
网友评论