手把手带你开发一个低代码可视化平台(四)硬核架构教程,全程开源同步开发

4,602

前文回顾

我开源的商城零代码可视化搭建平台Mall-Cook受到大家喜爱,使我深受鼓励。本着授人以鱼不如授人以渔思想,在项目新建shelf分支,带大家从零开发一个自己的可视化搭建平台。

前面我们已经:

  • 介绍了可视化搭建的架构,开发了可嵌套搭建面板 可视化搭建平台架构

  • 介绍了物料JSON构建属性面板流水线,开发了基础类型与复合类型属性的构建 构建流水线

本章目的

前面我们已经开发了物料JSON构建属性面板流水线,通过物料JSON这张身卡来解析构建物料属性面板。

因为JSON是无规则的,所以我们定义了JSON Schema规范约束JSON。

可按照规范编写JSON费时费力,且容易出错,为了避免手动繁琐编写的JSON,我们选择开发Json Schema生成器,通过可视化组装属性面板,生成符合平台JSON Schema规范的JSON,补上整条流水线的最后一环。

开发Schema生成器,补上流水线最后一环

Json Schema生成器

类似可视化搭建,我们通过拖拽各类型的物料操作组件,可视化的组装属性面板,然后解析成JSON。

基础演示 基础演示

前文我们介绍了复合类型,所以需提供组件嵌套,实现object、array类型的容器嵌套

对象类型 对象类型

对象数组类型 对象数组类型

构建标题物料属性面板

我们举个栗子可视化组装标题物料的属性面板

标题物料的属性面板 标题物料的属性面板

Schema生成器构建 Schema生成器构建

导出JSON 导出JSON

开发

接下来我们来开发Json Schema生成器,功能主要分为:

  • 注册属性类型操作组件JSON
  • 开发生成器可嵌套搭建面板
  • 生成JSON

生成器可嵌套搭建面板

开发逻辑每个类型操作组件包里,也有一个initializing.js作为它的身份卡。然后我就根据这个规则,利用require.context遍历custom-schema-template包下文件,找到所有initializing.js进行注册

注册类型操作组件,创建$cmpConfig(操作组件描述数组)

// 获取所有属性配置组件 initializing
function registerSchemaInitializing() {
    const files = require.context("@/custom-schema-template", true, /initializing.js$/);
    const list = [];

    files.keys().forEach((key) => {
        let name = key.replace(/(\.\/|\/initializing.js)/g, "");
        let [, , componentName] = key.split('/')  // 获取组件名

        list.push({ component: componentName, ...files(key).default })

    });

    Vue.prototype.$cmpConfig = list
}

开发生成器可嵌套搭建面板

我们使用的拖拽插件是vuedraggable,主要逻辑为拖拽模板物料到页面面板,深拷贝物料配置数据到页面配置中。

<!-- 生成器搭建面板 -->
<template>
  <div class="canvas">
    <!-- 页面面板 -->
    <div class="canvas-center">
      <div class="canvas-center-drag">
        <schema-content-item
          :componentList.sync="content.model.componentList"
        ></schema-content-item>
      </div>
    </div>

    <!-- 物料模板列表 -->
    <div class="canvas-left">
      <draggable
        v-model="$cmpConfig"
        :options="{ group: { name: 'itxst', pull: 'clone' }, sort: false }"
        :clone="handleClone"
        animation="300"
      >
        <div
          v-for="(item, index) in $cmpConfig"
          :key="index"
          class="canvas-left-item"
        >
          <span class="f13">{{ item.label }}</span>
          <span class="canvas-left-item-type">{{ item.type }}</span>
        </div>
      </draggable>
    </div>

    <!-- 属性面板 -->
    <div class="canvas-right">
      <component
        v-if="content.curComponent"
        :is="curComponentConfig"
        v-bind="content.curComponent"
      ></component>
      <schema-component v-else></schema-component>
    </div>
  </div>
</template>

<script>
export default {
  inject: ["content"],

  computed: {
    curComponentConfig() {
      return `${this.content?.curComponent?.component}Config`;
    },
  },

  methods: {
    // 拷贝模板,生成组件
    handleClone(model) {
      return {
        ...this._.cloneDeep(model),
        id: this.$getRandomCode(8),
        property: `${model.type}_${this.$getRandomCode(2, false)}`,
      };
    },
  },
};
</script>

想要实现可嵌套物料主要有三点:

  • 物料中包含slot,已供子物料存放
  • 物料配置中增加children属性,用于存放子物料配置
  • 递归渲染物料实现无限层级嵌套
<!-- 递归可嵌套组件 -->
<template>
  <draggable
    v-model="list"
    group="itxst"
    ghostClass="ghost"
    chosenClass="chosen"
    selector="selector"
    :animation="500"
    :sort="true"
    :class="[isChild ? 'drag-child' : 'drag-area']"
  >
    <schema-content-shape v-for="item in list" :key="item.id" :data="item">
      <component
        :is="item.component"
        :component-key="item.id"
        :edit="true"
        v-bind="item"
        :class="[['object', 'array'].includes(item.type) ? '' : 'event-none']"
      >
        <!-- 包含slot的组件才能进行嵌套渲染 -->
        <schema-content-item
          :componentList.sync="item.child"   // 子物料列表
          :isChild="true"                    // 是否为子物料
        ></schema-content-item>
      </component>
    </schema-content-shape>
  </draggable>
</template>

编译生成JSON

组合把组件列表解析成JSON,由于存在复合类型,所以属性(fields)有可能为树状结构

// 编译物料JSON
parsing () {
  this.config = {
    label: this.content.model.label,
    icon: this.content.model.icon,
    fields: {}
  }
  this.content.model.componentList.map(cmp => {
    this.parsingFields(cmp, this.config.fields)
  })
  this.isComplete = true
},
    
// 递归解析物料属性
parsingFields (config, fields) {
  let { property, label, type, child, value, data, options } = config
  let target = (fields[property] = ['object', 'array'].includes(type)
    ? { label, type }
    : { label, type, value })

  data && (target.data = data)
  options && (target.options = options)

  if (child) {
    target.child = {}
    child.map(c => this.parsingFields(c, target.child))
  }
}

下一节预告

  • 开发多页面构建
  • 开发模板项目,实现实时预览与封面自动生成

系列文章

交流群

交流群二维码可在项目github首页查看Mall-Cook主页