Skip to content

自定义组件

自定义组件功能使表单拥有很好扩展性,可能的应用场景如下:

  • 我需要写一个异步加载的搜索输入框(普适性不高/难以用 schema 描述的组件)
  • 我需要在表单内部写一个 excel 上传按钮(完全定制化的需求)

自定义组件可以选择局部注册,也可选择全局注册:

  • 局部注册:仅在某个表单中使用。
  • 全局注册:可以在全部的表单中使用,通常是重复使用的组件。

编写组件

组件与普通表单组件基本一致,但是有以下这些表单属性会被传入:

属性类型说明
itemIDynamicFormItem当前表单项的数据定义
disabledboolean当前表单项是否禁用
rawModelIDynamicFormObject整个表单的值
parentModelIDynamicFormObject父表单元素的值 (上一级的值,只在列表场景的使用,例如列表某个元素的父级就是整个 item)
[双向绑定主属性名称]unknown当前双向绑定变量的值,“双向绑定主属性名称” 是 DynamicFormItemRegistry.register 注册时指定的
namestring当前表单项的数据键值名称

另外,用户可以指定 additionalProps、additionalEvents 和 additionalDirectProps, 这些属性与事件都会被绑定到你的组件上。

事件:

为了实现数据双向绑定,发出 'update:[双向绑定主属性名称]' 事件用于数据更改事件。(“双向绑定主属性名称” 是 DynamicFormItemRegistry.register 注册时指定的)。

如下,这是一个自定义 check-box 的示例:

vue
BaseCheck.vue
<template>
  <div>
    <input
      class="dynamic-form-base-control base-checkbox"
      type="checkbox"
      :modelValue="value"
      :id="name"
      @update:modelValue="onValueUpdate"
      @change="(e: Event) => onValueUpdate((e.target as HTMLInputElement).checked)"
      @blur="onBlur"
    />
    <label :for="name">{{text}}</label>
  </div>
</template>

<script lang="ts" setup>
import { useInjectFormItemContext } from "../DynamicFormBasicControls";

defineProps({
  //这是传入的数据
  value: {},
  //你可以定义其他自定义属性,可以通过 additionalProps 传入。
  name: {
    type: String,
    default: '',
  },
  text: {
    type: String,
    default: '',
  },
});

const emit = defineEmits([ 'update:value' ]);

//注: useInjectFormItemContext 是UI组件库的表单项上下文,是为了
//表单校验所设置的,每个UI组件库都不同,具体请参考对应的文档
const formContext = useInjectFormItemContext();

function onBlur() {
  formContext.onFieldBlur();
}
function onValueUpdate(v: boolean) {
  emit('update:value', v);//这里是为了数据可以双向绑定
  formContext.onFieldChange(v);
}
</script>

全局注册

注:全局注册在运行中只需要注册一次即可。

js
import BaseCheck from 'BaseCheck.vue';

DynamicFormItemRegistry.register('base-check', markRaw(BaseCheck), {}, 'value');//指定传入主属性是 “value”

局部注册

在表单属性的 widgets 中注册,这个组件可以在当前表单中使用,参数与 DynamicFormItemRegistry.register 一致:

ts
import BaseCheck from 'BaseCheck.vue';
import { type IDynamicFormOptions, makeWidget } from "@imengyu/vue-dynamic-form";

const formOptions = ref<IDynamicFormOptions>({
  widgets: {
    'base-check': makeWidget(markRaw(BaseCheck), {}, 'value'),
  },
  formItems: [
    ...
  ]
});

一些情况下不需要包装自定义组件

自定义组件就是普通的 Vue 组件,唯一的要求是要有一个双向绑定值。所以如果现成的组件已经有了双向绑定的主属性,就可以直接拿来用,只需要在注册时指定主属性的名称。

举例来说:现在我们需要使用“级联选择”组件,这时打开 arco desgin 文档,我们看到 cascader 默认使用了 model-value (v-model),那就直接拿来用吧:

ts
import { Cascader } from "@arco-design/web-vue";
  
DynamicFormItemRegistry.register('cascader', markRaw(Cascader), {}, 'modelValue'); //指定传入主属性是 “modelValue”

注册后即可使用:

传入的 additionalProps 就是这个组件的属性,如果你使用TypeScript,具体 arco 为我们写好了定义,也可以直接导入,这样就有有类型定义了。

ts
import { CascaderInstance } from "@arco-design/web-vue";

const formOptions = ref<IDynamicFormOptions>({
  formItems: [
    { 
      type: 'cascader', label: '级联组件', name: 'test',
      additionalProps: {
        placeholder: '请选择发货地区' 
      } as CascaderInstance['$props'] //这里导入了 CascaderInstance 为了有类型定义
    },
  ]
});

Released under the MIT License.