json-schema-vue3

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容