# 扩展组件

接下来演示如何开发一个业务组件,以随机字符串输入为例。

需求如下:

  • 点击“生成”按钮自动生成字符串
  • 支持设置字符串长度
  • 支持设置输入框提示信息

扩展自定义组件,主要分为以下步骤:

  1. 功能实现:编写vue代码实现业务功能;
  2. 组件定义:编写Schema描述文件,定义组件及其属性;
  3. 打包测试:命令行打包组件进行测试;

# 效果预览

实现的目标是在画布里面使用自定义的组件

预览运行效果

# 功能实现

# 创建组件

ui/module/components 创建 RandomInput.vue 组件,并在同目录下的 index.ts 中导出。

RandomInput.vue

<template>
 <div></div>
</template>
<script lang="ts">
export default {
 ...
}
</script>

index.ts

/** 导出当前目录的扩展组件包 */
import RandomInput from './RandomInput.vue'
export default {
  RandomInput
}

# 开发组件

接下来,我们对 RandomInput.vue 进一步编码,以满足上诉需求。

<template>
  <div>
    <Input v-model="code" :placeholder="placeholder">
      <Button slot="append" @click="generate">生成</Button>
    </Input>
  </div>
</template>
<script lang="ts">
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
export default {
  props: {
    value: {
      type: String,
      default: null
    },
    len: {
      type: Number,
      default: 8
    },
    placeholder: {
      type: String,
      default: '请输入'
    }
  },
  data() {
    return {
      code: this.value
    }
  },
  watch: {
    code(val) {
      this.$emit('input', val)
      this.$emit('on-change', val)
    }
  },
  methods: {
    generate() {
      let result = ''
      for (var i = this.len; i > 0; --i) {
        result += chars[Math.floor(Math.random() * chars.length)]
      }
      this.code = result
    }
  }
}
</script>

# 测试组件

功能开发完成后,我们需要对自定义组件进行测试。 在 uitest.vue 页面引入需要测试的组件。命令行进入 ui 目录,执行 npm run dev ,访问测试的地址:http://localhost:9000/#/uitest 即可进行功能测试。

<template>
  <div class="m-md">
    <RandomInput v-model="text" :len="10" />
    <span>输入: {{ text }}</span>
  </div>
</template>

<script>
import RandomInput from '../../module/components/RandomInput.vue'
export default {
  components: {
    RandomInput
  },
  data() {
    return {
      text: null
    }
  }
}
</script>

<style></style>

测试效果

至此,自定义组件功能已经开发并测试完成。 在进入下一步之前,在 ui 工程下执行 npm run pack ,打包好的组件在 ui/dist/pack 目录下。

# 组件定义

要在页面设计器中使用,必须编写Schema,它是一份描述信息,定义了组件的元数据信息,包括属性,事件,交互等等。Schema是指对组件及其属性的描述。

# 编写Schema

ui/module/schemas/src/components 目录下创建 RandomInput.json 描述文件。

{
    "title": "随机字符输入",
    "description": "",
    "group": "自定义/表单",
    "orderNo": 300,
    "icon": "",
    "customicon": "idesignfont ides-form",
    "defType": "component",
    "htmlAttrs": "on",
    "properties": {
        "value": {
            "type": "string",
            "title": "值",
            "group": "基础",
            "orderNo": 0
        },
        "placeholder": {
            "type": "string",
            "title": "提示信息",
            "group": "基础",
            "default": "请输入",
            "orderNo": 1
        },
        "len": {
            "type": "number",
            "title": "长度",
            "group": "基础",
            "default": 8,
            "orderNo": 2
        }
    },
    "events": {
        "on-change": {
            "title": "输入内容改变事件",
            "description": "",
            "group": "交互",
            "orderNo": 0
        }
    }
}

# 属性定义

组件的属性定义,大致的格式如下:

{
 "placeholder": {  // 属性名
      "type": "string", 
      "title": "提示信息",
      "group": "基础",
      "default": "请输入",
      "orderNo": 1
  }
}

属性的定义,常见的几个 key:

分类 描述 默认值
type 属性的数据类型 ,支持 string,number,boolean,object,array string
title 属性标题
description 属性详细描述
group 属性分组,如不设置会放到“其他”里面
default 属性默认值
orderNo 属性排序,数值越小,越靠前
inputType 用来显示的输入组件: SingleLineText(单行文本输入) Number(数字输入) SingleSelect(下拉单选)MultipleSelect(下拉多选)Boolean(开关)MapEditor (key-value 输入) SingleLineText
expression 表达式的支持模式包括: on(支持) off(关闭) only(仅支持表达式) on
mock 模拟数据,设计时生效

# 常用inputType参考

SingleLineText(单行文本输入)

"properties": {
  "title": {
      "type": "string",
      "inputType": {  // 可以是字符串,也可以用复杂对象来定义
        "type": "SingleLineText",
        "props": {
          "type": "textarea", // 指定为多行文本
          "rows": 3
        }
      },
      "title": "左侧标题",
      "titleMode": "block",
      "group": "基础配置",
      "orderNo": 0,
      "default": "左侧标题"
    },
}

SingleSelect(下拉单选)

"properties": {
  "type": {
    "type": "string",
    "inputType": "SingleSelect",
    "title": "风格",
    "group": "基础配置",
    "orderNo": 0,
    "default": "style1",
    "enums": [  // 枚举值
      {
        "value": "style1",
        "title": "圆点风格"
      },
      {
        "value": "style2",
        "title": "圆圈风格"
      }
    ]
  }
}

MapEditor(key-value 输入)

"properties": {
  "headers": {
    "type": "object",
    "inputType": {
      "type": "MapEditor",
      "props": {
        "options": {
          "keyTitle": "请求头名",
          "valueTitle": "请求头值",
          "addTitle": "添加请求头"
        }
      }
    },
    "title": "请求头部",
    "titleMode": "none",
    "group": "请求头部",
    "orderNo": 0,
    "expression": "off"
  }
}

# 事件定义

组件的事件定义,大致的格式如下:

{
  "events": {
      "on-change": {  // 事件名,和组件实现代码里面 emit 的事件名称要保持一致
          "title": "输入内容改变事件",
          "description": "",
          "group": "交互",
          "orderNo": 0
      }
  }
}

事件的定义,常见的几个 key:

分类 描述 默认值
title 事件标题
description 事件详细描述
group 事件分组
orderNo 属性排序,数值越小,越靠前

# 方法定义

组件的方法定义,大致的格式如下:

{
  "methods": {
      "getBizData": {
        "title":"读取业务数据",
        "orderNo": 1,
        "parameters": [
          {
            "title": "数据key"
          }
        ]
      }
  }
}

如需在编排中执行异常处理时,函数需要return false 或者 return { error : true }

<template>
  <div>
    <Input v-model="code" :placeholder="placeholder">
      <Button slot="append" @click="generate">生成</Button>
    </Input>
  </div>
</template>
<script lang="ts">
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
export default {
  props: {
    value: {
      type: String,
      default: null
    },
    len: {
      type: Number,
      default: 8
    },
    placeholder: {
      type: String,
      default: '请输入'
    }
  },
  data() {
    return {
      code: this.value
    }
  },
  methods: {
    getBizData(){
      return false
      // return { error : true }
    }
  }
}
</script>

# 打包测试

编写完成 Schema 后,需要执行以下命令打包测试:


# 合并生成schema.json, 路径为 module/schemas/schema.json
npm run schema

 # 打包模块
npm run pack


# 拷贝 dist/pack/modules 以及 module/schemas/schema.json 到 app/ui/modules/ 目录下
npm run copyModule

执行成功后,就可以在 IDE 中正常使用自定义组件了。至此,我们就完成了自定义组件的开发,可以根据业务的实际需要自由扩展了!

# 高级定制

# 支持插槽

自定义的组件实现插槽,需要增加 "container": true 配置,否则无法启用。 同时,需要在 Schema 里面,声明 slots。

RandomInput.vue 里面写入一个 slot 区域。

<template>
  <div>
    <Input v-model="code" :placeholder="placeholder">
      <Button slot="append" @click="generate">生成</Button>
    </Input>
    <!-- 扩展一个插槽,这里允许放入其他组件 -->
    <slot name="footer"></slot>
  </div>
</template>
<script lang="ts">
const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
export default {
  props: {
    value: {
      type: String,
      default: null
    },
    len: {
      type: Number,
      default: 8
    },
    placeholder: {
      type: String,
      default: '请输入'
    }
  },
  data() {
    return {
      code: this.value
    }
  },
  watch: {
    code(val) {
      this.$emit('input', val)
      this.$emit('on-change', val)
    }
  },
  methods: {
    generate() {
      let result = ''
      for (var i = this.len; i > 0; --i) {
        result += chars[Math.floor(Math.random() * chars.length)]
      }
      this.code = result
    }
  }
}
</script>

RandomInput.json 里面增加配置。

{
 "container": true, // 需要设置该属性,不然插槽无法使用
 "showDefaultSlot": false, // 默认不显示插槽区域
 "slots": {
        "footer": {  // 插槽名称和代码里面定义的保持一致
            "title": "底部内容",
            "class": "",
            "turnOn":  true
          }
    }
}

# 作用域插槽

{
      "slots":{
        "footer": { // 插槽名称和代码里面定义的保持一致
            "title": "底部内容", 
            "turnOn": true,
            "context": {
              "name": "context", // 自定义 用于接收作用域插槽传递参数
              "properties": {
                "propKey":{  //自定义 对应作用域插槽传递参数的key
                    "type": "object"
                }
              } 
            }
          }
    }
}
<slot name="footer" :propKey="data"></slot>

在ide中使用 context.propKey 获取作用域插槽传递的参数。

例:

<template>
  <div>
    <p>UserInfo Component</p>
    <slot :user-info="userInfo"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userInfo: {
        name: '张三',
        age: 18
      }
    }
  }
}
</script>
{
    "title": "用户信息",
    "description": "",
    "group": "自定义/表单",
    "orderNo": 300,
    "icon": "",
    "customicon": "idesignfont ides-form",
    "defType": "component",
    "htmlAttrs": "on",
    "container": true,
    "slots":{
        "default": {
            "title": "默认插槽", 
            "context": {
              "name": "context", 
              "properties": {
                "userInfo":{
                    "type": "object"
                }
              } 
            },
            "turnOn": false
          }
    }
}

ide使用该组件时,通过context.userInfo获取作用域插槽传递的参数。

# 区分设计器和运行时

在组件中,可以通过 designerService 来控制当前组件在 设计器和运行时的行为。

<template>
  <div>
    <p>UserInfo Component</p>
    {{ !!designerService ? '设计模式' : '运行时' }}
  </div>
</template>

<script>
export default {
  inject: ['designerService'],
  data() {
    return {
    }
  }
}
</script>
顶部