可视化编辑器(schema设计)

1,263 阅读6分钟

前言

在上一篇动态表单设计文章详细讲解了动态表单的设计、具体的数据轮流转。接下来讲解一下我的问卷可视化编辑器的schema是如何设计的。

Schema 拆分设计

首先来看看动态表单设计的定义schema 55e4d959dd4e40ba86dabee01433d154~tplv-k3u1fbpfcp-jj-mark_3024_0_0_0_q75.jpg 属于大包大揽的设计,这种设计随着编辑器功能变多,schema会变得非常臃肿庞大会导致:

  1. 整个项目加载变慢
  2. 修改维护变得异常困难
  3. 组件之间schema变动相互影响

针对如上问题,我针对编辑器的schema设计进行了优化,让schema像乐高积木一样模块化、可复用、按需求加载

1. 按组件拆分schema

components/SO(单选)
├── config.js      # 组件schema定义
├── config.vue     # 配置区域视图组件
├── index.js       # 物料列表schema定义 (图片、组件名称、分类等)
└── index.vue      # 编辑器区域视图组件

SO的config.js定义单选按钮组件属性、MSS的config.js定义矩阵多选子题组件属性。其他更多组件以此类推,实现了每个组件之间的模块化、可扩展性 image.png

2. 公共部分抽离

把重复的公用的属性定义到PublicConfigClass中。 image.png 在其他schema中只需要通过extend继承这个PublicConfigClass基类既可以。我们还可以在当前组件覆盖掉基类属性值,例如version=1.1.0表示使用基类默认的version=1.0.0 image.png 实现复用性以及可扩展性,,假如想额外的定义样式的公共属性只需要在声明PulicStylesConfig即可。

3. 物料列表schema与编辑器schema分离

动态表单设计会把物料列表的schema耦合到编辑器schema中,例如物料的图片地址、类别等是不需要在渲染引擎体现的,因此进行了拆分 image.png image.png

4. 动态加载组件以及配置信息(按需加载)

// 双击物料组件将组件添加到编辑区
const dblclickHandle = async (item) => {
  // 动态注册组件
  componentInstall(item.viewKey, fetchViewComponent(item));
  componentInstall(item.conKey, fetchConfigComponent(item));
  // 创建新组建
  const newComponent = await createComponent(item);
  ...
};

动态的加载schema文件

// $ 配置信息缓存,提升组件加载速度
const componentCache = new Map();
/** 获取config.js文件 */
const loadConfig = (packageName, categoryName, keyName) => {
    const key = packageName + categoryName + keyName
    if (!componentCache.has(key)) {
        componentCache.set(key, import(`./components/${packageName}/${categoryName}/${keyName}/config.js`))
    }
    return componentCache.get(key)
}

/** 获取目标的配置信息 */
export const createComponent = async (targetData) => {
    const { category, key } = targetData

    const config = await loadConfig(targetData.package, category, key)
    return new config.default()
}

动态加载物料的组件(视图/配置)

// 遍历获取所有题库的组件
const configModules = import.meta.glob('./components/**/config.vue', { eager: true });
const indexModules = import.meta.glob('./components/**/index.vue', { eager: true });

/**
 * 获取组件
 * @param {string} name 
 * @param {boolean} isView 
 */
const fetchComponent = (name, isView) => {
    // 判断是加载的是config还是index组件
    const module = isView ? indexModules : configModules
    for (const key in module) {
        const urlSplit = key.split('/')
        const componentName = urlSplit[urlSplit.length - 2]
        // 找到对应组件的文件
        if (componentName === name) {
            return module[key]
        }
    }
}

/** 获取展示组件 */
export const fetchViewComponent = (item) => {
    const { key } = item
    return fetchComponent(key, true).default
}

/** 获取配置组件 */
export const fetchConfigComponent = (item) => {
    const { key } = item
    return fetchComponent(key, false).default
}
  • 总结
    经过如上四个步骤优化,使得编辑器具备
  1. 加载更快: 只加载当前需要的Schema
  2. 更好维护性: 修改单选组件定义不会影响到其他组件定义
  3. 更易扩展: 新增组件只需要添加一组config.jsconfig.vueindex.jsindex.vue即可
  4. class定义schema优点: 使得schema支持继承和组合、new实例不影响模版schema数据等

schema体积优化

在针对复杂的组卷时,其会包含很多题目组件会导致整张试卷的schema过于庞大,而Schema拆分设计来提升了可维护性加载效率以及可扩展性,但没有真正的减少schema的体积,下面是我对schema的一些体积优化考量

1. 极致简化属性名

// 优化前(32字节)
{
  "fontSize": "16px",
  "fontWeight": "bold"
}

// 优化后(18字节,减少43%)
{
  "fs": "16px",  // fontSize → fs
  "fw": "bold"   // fontWeight → fw
}

2. 枚举值数字化

// 优化前(48字节)
{
  "alignment": ["left", "center", "right"],
  "size": ["small", "medium", "large"]
}

// 优化后(26字节,减少45%)
{
  "align": [0, 1, 2],  // 0=left, 1=center, 2=right
  "sz": [1, 2, 3]      // 1=small, 2=medium, 3=large
}

3. 结构扁平化

// 优化前(多层嵌套)
{
  "style": {
    "text": {
      "color": "#333",
      "size": "14px"
    }
  }
}

// 优化后(扁平结构)
{
  "textColor": "#333",  // style.text.color → textColor
  "textSize": "14px"    // style.text.size → textSize
}

4. 使用数组代替对象

// 优化前(每个对象有键名)
{
  "items": [
    { "id": 1, "type": "text" },
    { "id": 2, "type": "image" }
  ]
}

// 优化后(固定顺序的数组)
{
  "items": [
    [1, "text"],   // [id, type]
    [2, "image"]
  ]
}

5. schema字典化

// 建立字典(服务端与客户端共享)
const DICT = [
  'fontSize', 'fontWeight', 'color', 
  'width', 'height', 'text'
];

// 传输时用索引代替键名
{
  "0": "16px",  // 0=fontSize
  "1": "bold"   // 1=fontWeight
}

这些方式都是根据失去可读性来实现缩减schema体积大小的方式。(如果你有更好的方案也可以分享给我哦,谢谢)

schema中的version

在单个组件的schema定义中会存在version版本属性 image.png 该属性主要用于确定加载当前组件的具体版本,因为我们的组件会被迭代升级,下一次schema的改动可能会不兼容当前的schema,因此为了不影响此前已经构建好的组件数据,引入了version的理念动态加载远程不同版本的物料组件,实现多版本共存 image.png

总结

通过上面的步骤,我们对schema拆分设计如何优化schema体积以及引入schema中version理念有了认知,此次设计到的代码源码,后期计划会进行编辑器出码编辑器的可插拔设计,希望这篇文章对大家有所帮助也祝大家万事如意