基于Ant-Design-Vue设计的配置化表单

适用vue 3.4 版本以上

在日常的前端开发中,表单开发必不可少,尤其是在遇到一些大型的复杂的表单,这往往会变成一个痛点。于是就想着开发一个可以list来配置的表单组件。

先上组件代码

typescript 复制代码
<!-- 该组件 VUE 版本在 3.4 以上可使用-->
<template>
  <Form
    ref="formRef"
    :model="model"
    v-bind='form'>
    <Row :gutter="[8, 8]">
      <template v-for="config in newConfigs" :key="config.key">
        <Col v-bind="config.wrapperCol" v-if="config.when(model)">
          <Form.Item v-bind="config.forImteAttrs"
            :label-col="config.labelCol">
            <component
              :is="config.type"
              v-bind="config.componentAttrs"
              :config="config"
              :raw="config.raw"
              :options="config.componentAttrs.options || options?.[config.key]"
              v-model:[config.model]="model[config.key]"
              v-on='{
                change: (e: any) => {
                  change(e, config.key);
                  config.componentAttrs.onChange?.({
                    update: change,
                    value: model
                  })
                }
              }'>
              {{ config.componentAttrs.buttonText || null }}
            </component>
          </Form.Item>
        </Col>
      </template>
      <slot />
    </Row>
  </Form>
</template>

<script setup lang="ts">
import {
  Col,
  Form,
  FormInstance,
  Row,
} from 'ant-design-vue';
import {
  computed, Ref,
  toRefs,
  useAttrs,
} from 'vue';
import { FormItemConfig } from './ConfigFormType';

const formRef = defineModel<Ref<null | undefined> | FormInstance>('formRef');
const model = defineModel<any>();

interface IConfigFormProps {
  layout?: 'inline' | 'vertical' | 'horizontal';
  FormGrid?: {};
  labelCol?: any;
  wrapperCol?: any;
  form?: any;
  configs: FormItemConfig[];
  options?: { [index: string]: any[] }
  readonly?: boolean;
}
const props = defineProps<IConfigFormProps>();
const {
  configs, FormGrid, labelCol, options, wrapperCol, readonly,
} = toRefs(props);

const generateUUID = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
  // eslint-disable-next-line no-bitwise
  const r = Math.random() * 16 | 0;
  // eslint-disable-next-line no-bitwise, no-mixed-operators
  const v = c === 'x' ? r : (r & 0x3) | 0x8;
  return v.toString(16);
});

const gloablAttrs = useAttrs();
const newConfigs = computed(() => configs.value.map((config: any) => {
  const {
    grid, key, type, attrs, itemModel, when, children, ...rest
  } = config;
  if (key) {
    rest.name = key;
  }
  if (!config.wrapperCol) {
    rest.wrapperCol = wrapperCol.value;
  }
  if (!config.labelCol) {
    rest.labelCol = labelCol.value;
  }
  const forImteAttrs: Partial<any> = rest;
  const componentAttrs = attrs || {};
  return {
    layout: { ...FormGrid, ...grid },
    name: [key || rest.name],
    key: key || rest.name || generateUUID(),
    when: when || (() => true),
    type,
    children,
    model: itemModel!,
    forImteAttrs,
    wrapperCol: rest.wrapperCol,
    labelCol: rest.labelCol,
    componentAttrs,
    readonly: gloablAttrs.readonly || readonly.value,
    disabled: gloablAttrs.disabled,
    raw: config,
  };
}));

interface IConfigFormEmits {
  (e: 'finish', value: any): void;
  (e: 'change', value: any): void;
}
const emit = defineEmits<IConfigFormEmits>();
const change = (e: any, key: string) => emit('change', { value: e, key });
</script>

具体使用

typescript 复制代码
<ConfigForm
	:configs="configComputed"
	v-model:formRef="formRef"
	v-model="formState"
	@finish="fetchInfo"
	layout="inline" />

configs

用来生成表单的具体配置,类型如下

typescript 复制代码
// FormItemConfig.ts
import { ColProps } from 'ant-design-vue';
import { Slot } from 'vue';

export type FormItemConfig = {
  title?: string;
  type: any;
  layout?: 'horizontal' | 'vertical' | 'inline';
  key?: string;
  name?: string;
  label?: string;
  rules?: any[] | any;
  colon?: boolean;
  options?: any[];
  hasFeedback?: boolean;
  help?: string | Slot;
  labelAlign?: 'left' | 'right';
  labelCol?: ColProps;
  required?: boolean;
  tooltip?: string | Slot;
  validateFirst?: boolean;
  validateStatus?: 'success' | 'warning' | 'error' | 'validating';
  validateTrigger?: string | string[];
  wrapperCol?: ColProps;
  extra?: string | Slot | any;
  model?: any;
  // eslint-disable-next-line no-unused-vars
  when?: (values: Partial<any>, currentValue?: Partial<any>) => boolean;
  itemModel?: string;
  attrs?: Partial<any>;
  children?: FormItemConfig[];
};

configs配置

1、attrs 属性为绑定到具体表单组件的属性,例如Select,Input之类的

2、其余大部分属性绑定到<Form.Item>组件上,故可以使用label,rules之类的属性,

3、itemModel一般情况下为value,type为Switch组件时,需根据实际情况变化

4、type属性是直接注入< components >组件的is属性上,所以一些自定义的组件也可以使用,注入到type组件中

typescript 复制代码
import {
  AutoComplete, Button, Spin,
} from 'ant-design-vue';
import {ref } from 'vue';

const autoCompleteLoading = ref<boolean>(false)
const configs = [
    {
      title: 'XXX',
      key: 'name',
      type: autoCompleteLoading.value ? Spin : AutoComplete,
      label: 'item 1',
      rules: { required: true, message: '请输入XXX', trigger: 'blur' },
      itemModel: 'value',
      attrs: {
        options: [{ label: 'item1', value: 'value1' }],
        allowClear: true,
        placeholder: 'XXXXXXXX',
        style: 'width: 280px',
        filterOption: (inputValue: string, option: any) => option.value
          .toLowerCase()
          .indexOf(inputValue.toLowerCase()) >= 0,
      },
    },
    {
      title: 'XXXXXXX',
      type: Button,
      attrs: {
        type: 'primary',
        htmlType: 'submit',
        buttonText: '查询',
      },
    },
   	];

也可以配合计算属性和响应式函数动态响应表单

typescript 复制代码
// config.ts
import { computed, Ref } from 'vue';
import {
  AutoComplete, Button, Spin,
} from 'ant-design-vue';


export function useConfigComputed(
	autoCompleteLoading:Ref<boolean>,
	dataNameList: Ref<{ label: string, value: any }[]>
) {
  return computed(() => [
    {
      title: 'XXX',
      key: 'name',
      type: autoCompleteLoading.value ? Spin : AutoComplete,
      label: 'item 1',
      rules: { required: true, message: '请输入XXX', trigger: 'blur' },
      itemModel: 'value',
      attrs: {
        options: [{ label: 'item1', value: 'value1' }],
        allowClear: true,
        placeholder: 'XXXXXXXX',
        style: 'width: 280px',
        filterOption: (inputValue: string, option: any) => option.value
          .toLowerCase()
          .indexOf(inputValue.toLowerCase()) >= 0,
      },
    },
    {
      title: 'XXXXXXX',
      type: Button,
      attrs: {
        type: 'primary',
        htmlType: 'submit',
        buttonText: '查询',
      },
    },
  ]);
}


// index.vue <script setup lang='ts'>
const autoCompleteLoading = ref<boolean>(false);
const dataNameList = ref<{ label: string, value: any }[]>([]);
const configs = useConfigComputed(
	autoCompleteLoading,
	dataNameList,
)
相关推荐
VT.馒头3 分钟前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
css趣多多14 分钟前
一个UI内置组件el-scrollbar
前端·javascript·vue.js
-凌凌漆-23 分钟前
【vue】pinia中的值使用 v-model绑定出现[object Object]
javascript·vue.js·ecmascript
C澒35 分钟前
前端整洁架构(Clean Architecture)实战解析:从理论到 Todo 项目落地
前端·架构·系统架构·前端框架
C澒41 分钟前
Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案
前端·架构·前端框架·状态模式
Charlie_lll44 分钟前
学习Three.js–雪花
前端·three.js
onebyte8bits1 小时前
前端国际化(i18n)体系设计与工程化落地
前端·国际化·i18n·工程化
C澒1 小时前
前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践
前端·架构·系统架构·前端框架
BestSongC1 小时前
行人摔倒检测系统 - 前端文档(1)
前端·人工智能·目标检测
0思必得02 小时前
[Web自动化] Selenium处理滚动条
前端·爬虫·python·selenium·自动化