Skip to content
本页索引

组件系统

组件是拖拽器最基本的物料,拖拽的操作对象就是组件。从实现方式上看,组件是一组围绕 vue组件 的代码,从功能上看,组件是提供一个独立精简小功能的模块。

组件构成

vue 单文件组件

这是一个普通的 vue 单文件组件,有自己的 propsevents,表单组件会有 v-model 数据。同时 iCreator 也会提供部分通用的属性和全局信息,以方便共用配置和统一控制。

profile 描述文件

本质上是一个 json 文件,用于描述 vue 单文件组件,它包括以下几个可能的根属性:editor, meta, attrs, slots, profile, limits, roles。这个 profile 描述文件的副本在拖拽器中修改完毕后,将作为状态数据保存入库(严格来说保存入库的数据并不完全是 profile 副本,只是大部分数据都来自于 profile 文件)。

点击查看详细的 json 字段描述
js
{
    "editor": {
        // 这里是组件在拖拽器中的配置,比如显示的图标/名称/位置等
        // 这个属性在入库前会被删除
    },
    "meta": {
        // 用来描述组件的元信息,比如关联的 vue 组件是哪个,
        // 默认占据的列宽是多少,v-model 是哪个属性,以及
        // 如果是容器组件,都支持哪些数据归集方式等等
        // 每类组件配置的内容都各不相同
    },
    "attrs": {
        // 这里描述的是 vue 单文件 对应的属性默认值,以 on 开头
        // 的被认为 events 属性,默认值需要是空数组 []
        // 这些属性值会被 iCreator 修改并保存在数据库中,这样就
        // 可以展示不同的状态和样式了
    },
    "slots": {
        // 这里描述的是 vue 单文件 的插槽属性,插槽属性在 iCreator 中
        // 做了简化,全部改用特殊格式的字符串描述,同时提供 slotRender
        // 做解析,UI 操作上可以手工编写,也可以通过可视化工具修改
    },
    "profile": {
        // 这里描述 attrs / meta / slots 中各个属性应该怎么编辑,都有什么可选值
        // 比如描述一个 layout 属性
        layout: {
          type: "string",
          label: "布局",
          enum: [
            {label: "居左", value: "left"},
            {label: "居中", value: "center"},
            {label: "居右", value: "right"},
          ],
          default: "left",
          order: 40
        },
        // 当然 profile 描述能力远不止于此,属性描述也支持函数
        // 具体可以参考代码中的已有样例或者自行查看解析引擎的逻辑
        // 描述非 attrs 时需要标注特殊前缀
        // 比如下方这个配置就是描述 meta.width 字段
        "$width": {},
        // 下方这个配置描述的是 slots.prefix
        "#prefix": {},
        // 嵌套字段可以通过以下方式声明,但通常不需要这么做
        "nest.field": {},
        // 如果不需要提供某一个预设属性的编辑,需要显示关闭
        "$width": null,
    },
    "limits": {
        // 这里保存是权限相关的数据,多数的组件只是空配置即可,iCreator
        // 会内置一个可使用权的配置。当复杂组件(比如多语言配置)需要
        // 具体配置某一个内部子模块权限时,可以此处声明,比如:
        editKey: {
            label: "Key编辑权",
            desc: "没有权限的角色将无法编辑多语言Key",
            order: 2
        }
    },
    // 这是表单组件特有的属性,用来保存组件的校验规则
    "rules": [],
}

migrate 脚本

组件的升级是一个可预期的事情,在 iCreator 中为了简化复杂度,并没有设计组件版本的概念,所有的组件都保持最新的一个版本。如果组件中途升级,就带来一个问题:旧组件的 profile 配置怎么兼容?

migrate 脚本就是为此而产生的,在一个新组件上线的时候,并不需要它。当有不兼容更新升级的时候,就需要这个脚本了。

migrate 脚本本质上是一组处理函数:root, meta, attrs, profile, rules, limits, slots。migrate 脚本可以包括一个或者多个处理函数。

migrate 脚本跟组件 profile 描述文件的根字段是一一对应的,在导入旧版本 profile 时进行动态修改。

比如 table 组件的 migrate 兼容脚本如下:

js
// 每个处理函数都会收到两个参数:
// ref 其对应的数据就是 组件 profile 描述 json 对应的字段(root 函数的 ref 是整个 profile)
// mode 对应 iCreator 的 mode 参数,可能值 form / view
export const attrs = (ref) => {
  delete ref.onSelect;
  delete ref.onSelectAll;
  if(!ref.expanded) {
    ref.expanded = [];
    ref.expandedCols = 2;
    ref.expandedUI = 'A';
  }
  ref.forceReloadKey = ref.forceReloadKey || 0;
}

组件复用

iCreator 看来,只要有一个组件 profile 描述就认为有一个组件,具体这个 profile 指定的是哪个 vue 组件,iCreator 并不限制。所以,这里就可以通过 profile 中的 meta 字段来复用 vue 组件。

比如在多选器组件和单选器组件,内部使用的就是同一个 vue 组件:

js
// 单选,使用的 vue 组件名为 cus-select
{
    "editor": {
        "name": "单选器"
        // ...
    },
    "meta": {
        "tag": "cus-select"
        // ...
    },
    "attrs": {
        // ...
    }
}
// 多选,使用的 vue 组件名也是 cus-select
// 不过 attrs 强制 mode: "multiple"
{
    "editor": {
        "name": "多选器"
        // ...
    },
    "meta": {
        "tag": "cus-select"
        // ...
    },
    "attrs": {
        "mode": "multiple",
        // ...
    }
}

表单组件

表单组件是一类特殊组件,包括 输入框,选择框,富文本等众多组件。跟其余组件有一个重要差别就是:

表单组件本身可以抛出一个数据,并提供数据的编辑修改功能

在实现层,指的就是 vue 组件有一个 v-model 数据,这通过 meta 字段来声明:

js
{
    "meta": {
        "vmodel": "value",
    	"key": "",
    	"defaultValue": "",
    }
}

有了 vmodel 配置就可以知道从vue组件哪个属性中获取组件值,有了 key 配置就知道把组件值放在哪个 key 下,有了 defaultValue 配置就知道如果用户不进行操作时应该是什么默认的组件值。

非表单组件没有上述三个配置。

数据组件

也称为视图组件,统称那些主要功能是展示数据或管理数据的组件,但组件本身并不抛出任何数据。数据组件中,最为重要的是 数据表格,它是管理数据的利器,是目前所有数据组件中最为复杂的一个组件。数据表格组件可以完成 “增删改查” 中的 “增删改”,而“查”的功能需要配合表单容器来完成。

容器组件

容器组件最核心的功能是一个“容器”,并兼顾其他功能。目前容器组件一共包含三个:

通用容器标签页表单容器

通用容器

通用容器是所有容器组件中最为复杂的一个,它有三类功能:

  1. 排版:这是容器组件的基本功能,能够将内部的组件当作一个整体进行布局和排版;

  2. UI 容器:通用容器默认是没有界面的,同时提供四种可选的 UI 外观;

  3. 数据整理:这是通用容器最为酷炫的功能,它可以伪装成一个表单组件,将自己内部表单组件的值“整理”到一起向外部抛出,抛出的数据格式支持两类:对象 object 或者 数组 array。而当整理格式为数组且子组件只有一个的时候,还支持 “数据展平” 配置。

    整理为对象

    假设组件内部拖入了两个输入框组件,分别可以抛出以下数据

    js
    // 假设第一个输入框组件抛出数据:
    {
    	"input1": "value1"
    }
    // 假设第二个输入框组件抛出数据:
    {
        "input2": "value2"
    }

    假设容器的字段名被设置 group1 则容器组件会抛出以下数据:

    js
    {
        "group1": {
            "input1": "value1",
            "input2": "value2"
        }
    }
    整理为数组

    假设组件内部拖入了两个输入框组件,分别可以抛出以下数据

    js
    // 假设第一个输入框组件抛出数据:
    {
    	"input1": "value1"
    }
    // 假设第二个输入框组件抛出数据:
    {
        "input2": "value2"
    }

    假设容器的字段名被设置 group1 则容器组件会抛出以下数据:

    js
    {
        "group1": [
            {
                "input1": "value1",
            	"input2": "value2"
            },
            // 上述部分是可重复的,
            // 重复次数在容器组件的“最小分组”和“最大分组”控制项中配置
        ]
    }
    数据展平

    当容器数据整理方式为数组,且内部只有一个组件时,可以直接将组件值展平。比如

    js
    // 容器内只有一个输入框组件,并产生以下组件值
    {
        "input1": "value1"
    }
    // 默认情况下,设置为数组的容器,将抛出以下数据:
    {
        "group1": [
            {
                "input1": "value1"
            }
        ]
    }

    可以看到示例中的容器抛出的数据,是存在不必要的层级的,此时如果打开容器的 “数据展平”,则容器将抛出以下数据:

    js
    {
        "group1": [
            "value1"
        ]
    }

    🔔 注意!请严格按照限定条件使用数据展平功能,如果数据展平后又拖入其他组件,将会导致旧数据无法导入编辑的问题!

    数据套娃🪆

    通过通用容器,可以完成几乎任意 json 数据的嵌套配置,日常所需的各种数据就可以轻而易举配置出来了。试试将以下表单数据配置出来:

    json
    {
        "user": {
            "name": "helloworld",
            "gender": 1
        },
        "job": {
            "company": {
                "city": "beijing",
                "name": "seayoo.com"
            },
            "work": "deveploer"
        },
        "hobby": [
            {
                "type": 1,
                "description": "football"
            },
            {
                "type": 2,
                "description": "photography"
            }
        ]
    }

注意

在视图模式下,通用容器将关闭数据整理功能,仅仅具有排版和 UI 功能。部分数据整理功能通过表单容器提供。

标签页

标签页是一个特殊的容器组件,除了基本的排版功能外,标签页仅仅支持 Tab 这一项 UI 类型。同时,标签页组件还具有以下几个特点:

  1. 数据整理仅仅支持对象,不支持数组;
  2. 一级子组件仅仅允许通用容器;

也就是说,标签页组件,将通用容器作为 Tab,然后将多个 Tab 作为一个整体,可以完成复杂的页面 UI 展示。其中数据整理方式不允许数组,仅仅是为了考虑理解成本,并非技术限制。

潜在的数据状态

容器组件本身是不允许有数据状态的,就像通用容器一样。但标签页组件本身隐含了一个数据状态:当前选中的 Tab 的值。在默认情况下,这个状态是被忽略的,无法记录也无法恢复,体现到页面交互上就是:你改变了标签页的 Tab,但是当页面重新加载后,仍将恢复为打开第一个 Tab。

通常这并不会造成什么问题,但当你想知道用户选择了哪个 Tab,并且需要恢复状态后,你可以采用两种方式之一处理:

  1. 不使用标签页,改用 单选按钮 + 通用容器,通过单选按钮的事件控制不同的容器显示或隐藏;
  2. 增加 “属性间谍” 组件,将标签页的属性 当前Tab(activeKey) 偷取出来进行上报以及恢复;

表单容器

表单容器仅仅允许在视图模式下使用。它的功能是一个精简的 form,默认以对象方式整理数据。它通常被用来当作搜索表单,给数据组件提供搜索选项。

它预设一个 “提交” 按钮,文字设置为 “搜索”,同时可选重设按钮。

同时,它又可以加载和渲染其他的内嵌表单,或者从自定义远程接口获取表单配置进行渲染。

可以参考 CURD 章节,进行搭配数据表格做搜索

特殊组件

iCreator 中有几个特殊的组件,需要一定的理解成本,这里展开介绍一下:

隐藏域属性间谍高级密码框聊天室

隐藏域

隐藏域是只有一项配置的组件,配置项是指定一个字段名。它不显示任何界面,类似于 <input type="hidden"/>

这个组件通常在传入一些不需要用户看到的校验数据时非常有用,比如在视图操作中,可以在加载修改表单前获取一个操作授权的 token,该 token 具有一定的时效性,当提交表单数据进行修改时,再次校验此 token,就可以判断操作授权是否超时了。

属性间谍

属性间谍默认也是一个隐藏的组件,不显示任何界面。它的主要功能就是:

获取其他组件的属性值作为自己的组件值,并对外抛出

最基础的一个场景是:由于容器组件不支持数据状态,可以使用属性间谍将其属性值获取并抛出,达到保存容器数据状态的目的:

  • step 1. 添加标签页,属性间谍组件
  • step 2. 打开属性间谍的属性:选择组件,选择 标签页 - 当前Tab(activeKey)
  • step 3. 打开属性间谍的属性:值同步

当前Tab 的值

默认情况下,当前Tab 的值是形如: tab1tab2 之类的顺序编号的 key,这是不可靠的,因为任何标签页的变更可能会导致 key 含义发生变化。通过给标签页下属通用容器设置一个特殊的组件名可以解决这个问题:

通用容器组件名格式为 label:key

标签页组件可以从下属的通用容器的组件名中提取 labelkey 信息,label 作为标签的 UI 显示名称,key 作为 当前Tab 的值。

高级密码框

高级密码框的外观就是密码框,“高级” 的原因在于组件抛出的值是经过特殊编码的,而且原文不支持导入修改,如果要修改,就只能全部重新设定一个新值。

混淆方式

组件目前支持两种混淆方式:偏移混淆加盐混淆,未来保留 密钥混淆(比如非对称加密)的扩展。

  • 偏移混淆

    默认情况下,混淆方式为偏移混淆,比如输入密码 123,那么组件抛出的值为 4118181920。重要的是,编码过程存在随机混淆数,输入同样的内容所产生编码基本都是不同的。

    🙈 具体的混淆和反混淆函数请参考内网 git 代码。

  • 加盐混淆

    混淆函数为:

    js
    function(str, salt) {
      if(!str || typeof str !== 'string' || !/^[a-zA-Z\d-]+$/.test(salt)) {
        return ''
      }
      const saltKey = escape(salt).split('').map(str => str.charCodeAt(0));
      return escape(str).split('').map((str, index) => {
        return ('00' + (str.charCodeAt(0) + saltKey[index % saltKey.length])).slice(-3);
      }).join('')
    }

    反混淆函数为:

    js
    function(estr, salt) {
      if(!estr || typeof estr !== 'string' || estr.length%3 !== 0) {
        return ''
      }
      const saltKey = escape(salt || '').split('').map(str => str.charCodeAt(0));
      const result = [];
      for (let index = 0; index < estr.length; index += 3) {
        result.push(String.fromCharCode(+estr.substr(index, 3) - saltKey[index / 3 % saltKey.length]));
      }
      return unescape(result.join(''))
    }

    escape & unescape

    escape 是 JavaScript 中用于编码字符串的方法,可以将非 Ascii 码字符转成 %uXXXX 格式的字符串,比如: escape("中") 会输出 %u4E2Dunescapeescape 的反向运算方法

    💀 escapeunescape 已经从web标准中剔除,组件的加盐混淆算法可能会修改

聊天室

这是一个应用级别的组件,它来源于 https://github.com/antoine92190/vue-advanced-chat,需要提供一组 API 才可以工作,目前(2022年8月)只有一个项目使用到了这个组件。

在组件的默认状态下,会有一些示例 Demo 聊天数据显示,里面有一些帮助信息。

该组件由于是独立 APP 级别的组件,完全可以应用到 2B 场景下,如果需要可以联系超级管理员