Skip to content

LdForm 表单组件

element-plus 的 form 组件进行封装,扩展一些常用的功能

写在前面

  • LdForm 是从 Vben Admin 迁移过来
  • Vue-Vben-Admin 是一个基于 Vue3.0、Vite、 Ant-Design-Vue、TypeScript 的后台解决方案

用法

useForm 方式

下面是一个使用简单表单的示例,只有一个输入框

<template>
  <LdForm ref="deptFormRef" @register="register" @submit="onSubmit"></LdForm>
</template>
<script>
import { defineComponent, ref, nextTick, unref } from "vue";
import { DeptService } from "@/service";
import { useToggle } from "@/landao/hooks";
import { useForm } from "@/components/LdForm";
import { ElMessage, ElMessageBox } from "element-plus";
import { deepTree } from "@/landao/utils";

export default defineComponent({
  name: "DeptForm",
  setup() {
    const deptFormRef = ref(null);

    //需要更新的部门ID
    const { state: updateDeptId, toggle: setUpdateDeptId } = useToggle(0);

    //注册表单
    const [register, { resetFields, setProps, setFieldsValue }] = useForm({
      labelPosition: "right",
      labelWidth: "150px",
      schemas: [
        {
          field: "parent_id",
          label: "上级部门",
          required: true,
          component: "ApiTreeSelect",
          defaultValue: 0,
          componentProps: {
            placeholder: "请选择上级部门",
            api: getList,
            filterable: true,
            checkStrictly: true,
            nodeKey: "dept_id",
            props: {
              label: "dept_name",
            },
            afterFetch: (data) => {
              data.unshift({
                dept_name: "一级部门",
                dept_id: 0,
              });
              return deepTree(data, "dept_id", "parent_id", "dept_order");
            },
          },
        },
        {
          field: "dept_name",
          label: "部门名称",
          component: "Input",
          required: true,
          defaultValue: "",
        },
        {
          field: "dept_desc",
          label: "备注",
          component: "Input",
          required: true,
          defaultValue: "",
          componentProps: {
            type: "textarea",
          },
        },
        {
          field: "dept_order",
          label: "排序",
          component: "InputNumber",
          defaultValue: 1,
          componentProps: {
            controlsPosition: "right",
            min: 0,
          },
        },
      ],
      showResetButton: false,
    });

    const onSubmit = async (data) => {
      setProps({ submitButtonOptions: { loading: true, label: "提交中..." } });
      if (unref(updateDeptId) > 0) {
        await DeptService.update(unref(updateDeptId), data)
          .then((res) => {
            ElMessage.success(res.message);
            clearTableForm();
          })
          .catch((err) => {
            ElMessage.error("1" + err);
          });
      } else {
        await DeptService.doStore(data)
          .then((res) => {
            ElMessage.success(res.message);
            clearTableForm();
          })
          .catch((err) => {
            ElMessage.error("2" + err);
          });
      }
      setProps({ submitButtonOptions: { loading: false, label: "提 交" } });
    };

    return {
      deptFormRef,
      register,
      onSubmit
    };
  },
});
</script>

Form 属性

属性类型默认值可选值说明版本
schemasFormSchema[]--表单配置,见下方 FormSchema 配置
labelPositionstring-'right', 'left', 'top'表单域标签的位置
labelWidthnumber , string--扩展 form 组件,增加 label 宽度,表单内所有组件适用,可以单独在某个项覆盖或者禁用
sizestringdefault'large', 'default', 'small'向表单内所有组件传递 size 参数,自定义组件需自行实现 size 接收
disabledbooleanfalse'false', 'true'向表单内所有组件传递 disabled 属性,自定义组件需自行实现 disabled 接收
showActionButtonGroupbooleantrue'false', 'true'是否显示操作按钮(重置/提交)
actionColOptionsObjecttrue'false', 'true'操作按钮外层 ElCol 组件配置,如果开启 showAdvancedButton,则不用设置,具体见下方 ColEx
showSubmitButtonbooleantrue'false', 'true'确认按钮配置见下方 ActionButtonOption
submitButtonOptionsObject确认按钮配置见下方 ActionButtonOption
showResetButtonbooleantrue'false', 'true'确认按钮配置见下方 ActionButtonOption
resetButtonOptionsObject确认按钮配置见下方 ActionButtonOption
showAdvancedButtonObject是否显示展开收起按钮

内置组件 FormAction 插槽

表单底部按钮组插槽

插槽名说明
resetBefore安放在重置按钮前
submitBefore安放在提交按钮前
advanceBefore安放在收起/展开按钮前
advanceAfter安放在收起/展开按钮后

Form 方法

方法名说明类型
setProps设置Form属性(formProps) => Promise<void>
resetFields重置表单为默认值()=>void
validate对整个表单的内容进行验证。 接收一个回调函数,或返回 Promise
clearValidate清除校验(name) => void
validateField验证具体的某个字段
scrollToField滚动到指定位置
getFieldsValue获取表单值()=>Recordable
setFieldsValue表单回显,设置表单值(values)=>void
resetSchema重置FormSchema(data) => void
updateSchema更新FormSchema项,只更新函数所传的参数(data) => void
//TODO:componentProps是函数的话,无效
updateSchema({ field: 'filed', componentProps: { disabled: true } });
updateSchema([
  { field: 'filed', componentProps: { disabled: true } },
  { field: 'filed1', componentProps: { disabled: false } },
]);

FormSchema 属性

属性类型默认值可选值说明
fieldstring--字段名
labelstring--标签名
labelWidthstring , number--覆盖统一设置的 labelWidth
componentstring--组件类型,见下方 ComponentType
requiredboolean--简化 rules 配置,为 true 则转化成 [{required:true}]
rules`FormItemRuleFormItemRule[]`--
colPropsColEx--参考下方 ColEx
componentPropsany,()=>{}--所渲染的组件内部 props,详见componentProps
renderComponentContent()=>{}--定义渲染组内部的 slot,详见renderComponentContent
slotstring--自定义 slot,见下文 solt
helpMessagestring--标签右侧温馨提示
helpComponentPropsObject--标签名右侧温馨提示组件 props, 部分继承 el-tootip 属性,见属性 helpComponentProps
ifShowboolean / ((renderCallbackParams) => boolean)true-动态判断当前组件是否显示,js 控制,会删除 dom,见下方ifShow/isshow ,renderCallbackParams
showboolean / ((renderCallbackParams) => boolean)true-动态判断当前组件是否显示,css 控制,不会删除 dom,见下方ifShow/isshow,renderCallbackParams
customRender(renderCallbackParams) => VNode / VNode[] / string--自定义渲染组件renderCallbackParams
ifRightShowbooleanfalsetrue|false是否显示右侧自定义内容
customRightRender(renderCallbackParams) => VNode / VNode[] / string--自定义右侧渲染组件,注意要设置左侧组件宽度,否则会破格renderCallbackParams
dynamicRulesboolean / ((renderCallbackParams) => boolean)--动态判返当前组件校验规则renderCallbackParams

renderCallbackParams

回调参数

return {
    field: schema.field,//当前字段名
    model: formModel,//双向绑定
    values: {//双向绑定值
      ...formModel,
    },
    schema: schema,//配置参数
};

ColEx

element-plus(Layout 布局)

componentProps

参数有 3 个

schema: 表单的整个 schemas

formModel: 表单的双向绑定对象,这个值是响应式的。所以可以方便处理很多操作

formActionType:操作表单的函数。与 useForm 返回的操作函数一致

{
  // 简单例子,值改变的时候操作表格或者修改表单内其他元素的值
  component:'Input',
  componentProps: ({ schema,  formModel }) => {
    return {
      onChange:(e)=>{
      }
    };
  };
}

renderComponentContent

element-plus(input-插槽)

{
  // 简单例子,组件内部slot
  component:'Input',
  renderComponentContent: () => {
    return {
        prefix: () => 'Search',
        append: () => ".com",
    };
}

slot

FormSchema 自定义插槽


<template>
 <div class="ld-form">
    <ld-form label-position="right" label-width="150px" :schemas="schemas">
        <!-- #slotName 或 v-slot:slotName -->
      <template #menuFileSlot="{ model, field, values }">
        <menu-file v-model="model[field]" />
      </template>
    </ld-form>
  </div>
</template>
<script>
  import { defineComponent } from 'compatible-vue';
  import menuFile from "@/views/system/components/menu-file";
  export default defineComponent({
    name: 'FormDemo',
     components: { menuFile },
    setup(props) {
      const schemas = [
      {
        field: "menu_component",
        label: "文件路径:",
        slot: "menuFileSlot",
      },
    ];

    return {
      schemas,
    };
    },
  });
</script>

ifShow/show

自定义显示

<template>
  <div class="m-4">
    <LdForm @register="register" />
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useForm } from "@/components/LdForm";
  const schemas = [
    {
      field: 'field1',
      component: 'Input',
      label: '字段1',
      colProps: {
        span: 8,
      },
      show: ({ values }) => {
        return !!values.field5;
      },
    },
    {
      field: 'field2',
      component: 'Input',
      label: '字段2',
      colProps: {
        span: 8,
      },
      ifShow: ({ values }) => {
        return !!values.field6;
      },
    }
  ];

  export default defineComponent({
    components: { BasicForm },
    setup() {
      const [register, { setProps }] = useForm({
        labelWidth: 120,
        schemas,
      });
      return {
        register,
        schemas,
        setProps,
      };
    },
  });
</script>

ComponentType

schema 内组件的可选类型

export type ComponentType =
  | 'Input'
  | 'Switch'
  | 'Radio'
  | 'RadioGroup'
  | 'InputNumber'
  | 'Rate'
  | 'Cascader'
  | 'Checkbox'
  | 'CheckboxGroup'
  | 'DatePicker'
  | 'TimeSelect'
  | 'TimePicker'
  | 'DateTimePicker'
  | 'DatesPicker'
  | 'ApiSelect'
  | 'Select'
  | 'ApiTreeSelect'
  | 'Divider'
  | "ApiCascader"
  | "SelectPage"

RadioGroup

注意

  • RadioGroup 是对 el-radio-group 进行封装
  • 若返回的值是['manage.passport.captcha', 'manage.passport.login'] 组件依然转换成:{label:value,value:value}

使用

const schemas = [
{
    field: "menu_type",
    component: "RadioGroup",
    label: "节点类型",
    colProps: {
      span: 8,
    },
    defaultValue: "0",
    componentProps: {
      options: [
        { label: "目录", value: "0" },
        { label: "菜单", value: "1" },
        { label: "权限", value: "2" },
      ],
    },
  },
];

RadioGroup 属性

属性类型默认值说明
typestring`''button`
borderbooleanfalse设置 border 属性为 true 可以渲染为带有边框的单选框。
sizestringlarge / default /smallRadio 的尺寸
optionsarray[]数据字段

ApiSelect 远程下拉组件

远程下拉组件,其他componentProps可参见el-select配置

注意

  • 组件内会将数据转换成{label:labelField,value:valueField,disabled:disabledField}
  • 若返回的值是['manage.passport.captcha', 'manage.passport.login'] 组件依然转换成:{label:value,value:value,disabled:false}

使用

const schemas = [
 {
    field: "api_path",
    label: "权限标识",
    required: true,
    component: "ApiSelect",
    defaultValue: "",
    componentProps: {
      placeholder: "请选择权限标识",
      api: MenuService.getPower,
      immediate: true,
      filterable: true,
    },
 }
];

ApiSelect 属性

属性类型默认值说明
api()=>{}-数据接口,接受一个 Promise 对象
paramsobject-接口参数。此属性改变时会自动重新加载接口数据
labelFieldstringlabel下拉数组项内label显示文本的字段
valueFieldstringvalue下拉数组项内value实际值的字段
disabledFieldbooleandisabled下拉数组项内disabled实际值的字段
immediatebooleantrue是否立即请求接口,否则将在第一次点击时候触发请求

ApiTreeSelect 远程树形下拉组件

远程树形下拉组件,含有下拉菜单的树形选择器,结合了 el-tree 和 el-select 两个组件的功能。

提示

使用

const schemas = [
  {
    field: "parent_id",
    label: "上级节点",
    required: true,
    component: "ApiTreeSelect",
    componentProps: {
      placeholder: "请选择上级节点",
      api: MenuService.getList,
      filterable: true,
      nodeKey: "menuId",
      props: {
        label: "title",
        children: "children",
      },
      checkStrictly: true,
      afterFetch: (treeData) => {
        if (!treeData) return [];
        let menuList = treeData.filter((item) => item.menuType != 2);
        menuList.unshift({
          title: "一级菜单",
          menuId: 0,
        });
        return deepTree(menuList);
      },
    },
  },
];

ApiTreeSelect 属性

属性类型默认值说明
api()=>{}-数据接口,接受一个 Promise 对象
paramsobject-接口参数。此属性改变时会自动重新加载接口数据
immediatebooleantrue是否立即请求接口,否则将在第一次点击时候触发请求
afterFetchnull()=>{}-

DatePicker 日期组件

使用

const schemas = [
  {
      field: "monthField",
      label: "月份范围",
      component: "DatePicker",
      colProps: {
        span: 8,
      },
      componentProps: {
        type: "monthrange",
      },
    },
    {
      field: "date_range",
      label: "日期范围",
      component: "DatePicker",
      defaultValue: "",
      colProps: {
        span: 8,
      },
      componentProps: {
        type: "daterange",
        format: "YYYY-MM-DD",
        rangeSeparator: "",
        startPlaceholder: "开始时间",
        endPlaceholder: "结束时间",
      },
    },
    {
      field: "created_range_time",
      label: "事件",
      component: "DateTimePicker",
      colProps: {
        span: 8,
      },
      componentProps: {
        format: "YYYY-MM-DD HH:mm:ss",
      },
    },
];

DatePicker 属性

属性类型默认值说明
typestringyear/month/date/daterange/monthrange日期组件类型
formatYYYY-MM-DD-日期格式化,若设置type:year/month/monthrange,需将format设置为:YYYY/YYYY-MM
isTimestampbooleanfalse是否返回时间戳 valueOf

DatesPicker 多选日期

提示

该组件是从 el-DatePicker 剥离下来,单独成立的一个组件

使用

const schemas = [
   {
      field: "fieldDates",
      label: "选中多个日期",
      component: "DatesPicker",
      defaultValue: ['2022-07-01','2022-07-25'],
      colProps: {
        span: 8,
      },
      componentProps: {
        format: "YYYY-MM-DD",
      },
    },
];

DatesPicker 属性

属性类型默认值说明
isTimestampbooleanfalse是否返回时间戳`valueOf
formatstring-日期格式化

DatePicker 属性

属性类型默认值说明
typestringyear/month/date/daterange/monthrange日期组件类型
formatYYYY-MM-DD-日期格式化,若设置type:year/month/monthrange,需将format设置为:YYYY/YYYY-MM
isTimestampbooleanfalse是否返回时间戳 valueOf

DateTimePicker 日期时间组件

提示

该组件是从 el-DateTimePicker 剥离下来,单独成立的一个组件,该组件只支持datetimerange/datetime两个类型

使用

const schemas = [
   {
      field: "fieldDates",
      label: "选中多个日期",
      component: "DateTimePicker",
      defaultValue: [],
      colProps: {
        span: 8,
      },
      componentProps: {
        isRange:true,
        type: "daterange",
        format: "YYYY-MM-DD HH:mm:ss",
        rangeSeparator: "",
        startPlaceholder: "开始时间",
        endPlaceholder: "结束时间",
      },
    },
];

DatesPicker 属性

注意事项

el-DateTimePicker内置属性type,不能使用。只能通过isRange来渲染组件类型

属性类型默认值说明
isRangebooleanfalse是否是日期时间范围,支持时间类型datetimerange/datetime
isTimestampbooleanfalse是否返回时间戳`valueOf
formatstring-日期格式化

SelectPage 下拉分页列表框

提示

该组件是从 el-select 演化而来,起初是自定义 el-select 内容,但是在嵌套级联和下拉框的时候,出现el-popover冲突。

此组件是组件LdForm的子组件,现又在该组件的搜索表单中嵌套LdForm组件。

慎用

SelectPage 属性

属性类型默认值可选值说明
api()=>{}null数据接口,接受一个 Promise 对象
modelValueobject|array-绑定数据
multiplebooleantrue是否多选
multiplenumber0多选时,最多选择几项
sizestringsmalllarge / default / small根据业务状态控制当前是否显示
placeholderstring请选择占位文字
columnsarray[]列配置,详细见下文:columns
widthnumber|string[]el-popover的宽度
placementstringbottomtop/top-start/top-end/bottom/bottom-start/bottom-end/left/left-start/left-end/right/right-start/right-endel-popover显示位置
valueKeystringvalue选项值key
labelKeystringlabel选项值标签key
contentMaxHeightnumber|string360列表内容区域宽度
formConfigarray[]-搜索表单配置,详见:formschema-属性

columns 列配置项

属性类型默认值说明
format(value, row, column)=>{}-格式化数据
propstring-读取数据集 key
labelstring列显示名称
widthnumber|string50列宽度

SelectPage 使用

<template>
  <SelectPage
        placeholder="请选择授课校区"
        v-model="schoolValue"
        :api="schoolPageApi"
        :columns="schoolColumn"
        :form-config="formConfig"
        value-key="school_id"
        label-key="school_name"
      >
        <template #search>
          <el-form-item prop="school_name">
            <el-input
              size="default"
              v-model="formSearch.school_name"
              placeholder="请输入校名"
            ></el-input>
          </el-form-item>
        </template>
      </SelectPage>
      <SelectPage
        placeholder="请选择授课校区,多选"
        v-model="schoolValueArr"
        :api="schoolPageApi"
        :columns="schoolColumn"
        value-key="school_id"
        label-key="school_name"
        multiple
      ></SelectPage>
</template>
<script>
import { defineComponent, reactive, ref } from "vue";
import { useBaseStore } from "@/store";
import { CourseService, SchoolService } from "@/service";
import SelectPage from "@/components/LdForm/components/widget/SelectPageWidget";
export default defineComponent({
  name: "CourseList",
  components: {
    CourseCategoryTree,
    SelectPage,
  },
  setup() {
     // 要删除掉的
    const { app: appStore } = useBaseStore();
    const { school_type } = appStore.dictionary;

    const schoolColumn = reactive([
      {
        prop: "school_name",
        label: "学校",
        width: 300,
      },
      {
        prop: "school_type",
        label: "类型",
        width: 100,
        format: (value, row, column) => {
          const constantItem = school_type.find((item) => item.value == value);
          return constantItem.label ?? "-";
        },
      },
    ]);
    const schoolValue = ref({ school_id: 1002, school_name: "福州16中" });
    const schoolValueArr = ref([
      { school_id: 1002, school_name: "福州16中" },
      { school_id: 1003, school_name: "福州19中" },
    ]);

    const formConfig = reactive([
      {
        field: "district_id",
        component: "Cascader",
        defaultValue: "",
        colProps: {
          span: 8,
        },
        componentProps: {
          placeholder: "请选择学校所在区域",
          options: appStore.operationArea,
          showAllLevels: false,
          props: { emitPath: false },
          clearable: true,
        },
      },
      {
        field: "school_name",
        component: "Input",
        defaultValue: "",
        colProps: {
          span: 8,
        },
        componentProps: {
          placeholder: "请输入学校名称",
          clearable: true,
        },
      },
    ]);
    return {
       schoolPageApi: SchoolService.page,
      schoolColumn,
      schoolValue,
      schoolValueArr,
      formConfig,
    }
  })

SelectPage 配合LdForm使用

 function getFormSchema() {
    return [
      {
        field: "schools",
        label: "开课校区:",
        component: "SelectPage",
        defaultValue: [],
        required: true,
        colProps: {
          span: 8,
        },
        componentProps: {
          api: SchoolService.simple,
          placeholder: "请选择校区",
          multiple: true,
          multipleLimit: 2,
          columns: [
            {
              prop: "school_name",
              label: "学校",
              width: 300,
            },
            {
              prop: "school_type",
              label: "类型",
              width: 100,
              format: (value, row, column) => {
                const constantItem = school_type.find(
                  (item) => item.value == value
                );
                return constantItem.label ?? "-";
              },
            },
          ],
          valueKey: "school_id",
          labelKey: "school_name",
          formConfig: [
            {
              field: "district_id",
              component: "Cascader",
              defaultValue: "",
              colProps: {
                span: 8,
              },
              componentProps: {
                placeholder: "请选择学校所在区域",
                options: appStore.operationArea,
                showAllLevels: false,
                props: { emitPath: false },
                clearable: true,
              },
            },
            {
              field: "school_name",
              component: "Input",
              defaultValue: "",
              colProps: {
                span: 8,
              },
              componentProps: {
                placeholder: "请输入学校名称",
                clearable: true,
              },
            },
          ],
        },
    ]
    }

注意事项

  • 隐藏表单项,在slot是InputNumber组件,隐藏的 component,也要设置InputNumber,否则会转化为string类型,
  • 表单多个 Divider 也要设置 field,否则在使用 updateSchema 方法的时候,会过滤相同 field

根据 MIT 许可证发布。