美文网首页让前端飞
【Vue3+Vite+TS】13.0 组件十:强大的表单组件(上

【Vue3+Vite+TS】13.0 组件十:强大的表单组件(上

作者: bobokaka | 来源:发表于2022-01-10 19:17 被阅读0次

    功能

      1. 可配置型表单,通过json对象的方式自动生成表单
      1. 具备更完善的功能:表单验证、自定义验证规则、动态删减表单、集成第三方的插件
      1. 用法简单,扩展性强,可维护性强
      1. 能够用在更多的场景,比如弹框嵌套表单

    准备工作

    1. 分析element-plus表单能够在那些方面做优化
    2. 完善封装表单的类型,支持ts
    3. 封装的表单要具备element-plus原表单的所有功能
    4. 集成第三方插件:markdown编辑器、富文本编辑器(
      比如WangEditor)

    必备UI组件

    将用到的组件:
    很多,涉及到表单的所有组件。
    第三方组件:WangEditor

    组件设计

    新建src\components\baseline\form\index.ts

    import { App } from 'vue'
    import Form from './src/index.vue'
    
    export { Form }
    
    //组件可通过use的形式使用
    export default {
      Form,
        install(app: App) {
            app.component('bs-form', Form)
        },
    }
    
    

    调整src\components\baseline\index.ts

    import { App } from 'vue'
    import ChooseArea from './chooseArea'
    import ChooseIcon from './chooseIcon'
    import Container from './container'
    import Trend from './trend'
    import Notification from './notification'
    import List from './list'
    import Menu from './menu'
    import Progress from './progress'
    import ChooseTime from './chooseTime'
    import ChooseDate from './chooseDate'
    import ChooseCity from './chooseCity'
    import Form from './form'
    const components = [
        ChooseArea,
        ChooseIcon,
        Container,
        Trend,
        Notification,
        List,
        Menu,
        Progress,
        ChooseTime,
        ChooseDate,
        ChooseCity,
        Form,
    ]
    export {
        ChooseArea,
        ChooseIcon,
        Container,
        Trend,
        Notification,
        List,
        Menu,
        Progress,
        ChooseTime,
        ChooseDate,
        ChooseCity,
        Form,
    }
    
    //组件可通过use的形式使用
    export default {
        install(app: App) {
            components.map(item => {
                app.use(item)
            })
        },
        ChooseArea,
        ChooseIcon,
        Container,
        Trend,
        Notification,
        List,
        Menu,
        Progress,
        ChooseTime,
        ChooseDate,
        ChooseCity,
        Form,
    }
    
    

    路由增加,调整src\router\index.ts

       {
                    path: '/form',
                    component: () => import('../views/baseline/form/index.vue'),
                },
    

    新增src\views\baseline\form\index.vue

    <template>
        <div class="bs-wrapper">
            <bs-form :options="options"></bs-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { FormOptions } from '@/components/baseline/form/src/types/types'
    
    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            rules: [
                {
                    required: true,
                    message: '用户名不能为空',
                    trigger: 'blur',
                },
                {
                    min: 2,
                    max: 10,
                    message: '用户名长度在2-10位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
            },
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    
    

    到此,基本结构搭建完毕。

    如果需要做到表单通过json自动配置组合,需要建立一套完整的ts类型限制。
    新建src\components\baseline\form\src\types\types.ts

    import { CSSProperties } from 'vue'
    import { RuleItem } from './rule'
    
    import { ValidateFieldsError } from 'async-validator'
    interface Callback {
        (isValid?: boolean, invalidFields?: ValidateFieldsError): void
    }
    
    /**
     * 表单每一项的配置选项
     */
    export interface FormOptions {
        // 表单项显示的元素
        type:
            | 'cascader'//级联选择器
            | 'checkbox'//多选框
            | 'checkbox-group'
            | 'checkbox-button'
            | 'color-picker'
            | 'date-picker'
            | 'input'
            | 'input-number'
            | 'radio'
            | 'radio-group'
            | 'radio-button'
            | 'rate'
            | 'select'
            | 'option'
            | 'slider'
            | 'switch'
            | 'time-picker'
            | 'time-select'
            | 'transfer'//穿梭框
            | 'upload'
            | 'editor'
        // 表单项的值
        value?: any
        // 表单项label
        label?: string
        // 表单项的标识
        prop?: string
        // 表单项的验证规则
        rules?: RuleItem[]//基于async-validator规则验证
        // 表单项的占位符
        placeholder?: string
        // 表单元素特有的属性
        attrs?: {
            // css样式
            style?: CSSProperties
            clearable?: boolean
            showPassword?: boolean
            disabled?: boolean
        }
        // 表单项的子元素
        children?: FormOptions[]
        // 处理上传组件的属性和方法
        uploadAttrs?: {
            action: string
            headers?: object
            method?: 'post' | 'put' | 'patch'
            multiple?: boolean
            data?: any
            name?: string
            withCredentials?: boolean
            showFileList?: boolean
            drag?: boolean
            accept?: string
            thumbnailMode?: boolean
            fileList?: any[]
            listType?: 'text' | 'picture' | 'picture-card'
            autoUpload?: boolean
            disabled?: boolean
            limit?: number
        }
    }
    
    export interface ValidateFieldCallback {
        (message?: string, invalidFields?: ValidateFieldsError): void
    }
    
    export interface FormInstance {
        registerLabelWidth(width: number, oldWidth: number): void
        deregisterLabelWidth(width: number): void
        autoLabelWidth: string | undefined
        emit: (evt: string, ...args: any[]) => void
        labelSuffix: string
        inline?: boolean
        model?: Record<string, unknown>
        size?: string
        showMessage?: boolean
        labelPosition?: string
        labelWidth?: string
        rules?: Record<string, unknown>
        statusIcon?: boolean
        hideRequiredAsterisk?: boolean
        disabled?: boolean
        validate: (callback?: Callback) => Promise<boolean>
        resetFields: () => void
        clearValidate: (props?: string | string[]) => void
        validateField: (props: string | string[], cb: ValidateFieldCallback) => void
    }
    

    新建src\components\baseline\form\src\types\rule.ts
    该文件从async-validator规则验证github项目中复制出来的,不需要自己思考文件内容。

    export type RuleType =
      | 'string'
      | 'number'
      | 'boolean'
      | 'method'
      | 'regexp'
      | 'integer'
      | 'float'
      | 'array'
      | 'object'
      | 'enum'
      | 'date'
      | 'url'
      | 'hex'
      | 'email'
      | 'pattern'
      | 'any';
    
    export interface ValidateOption {
      // whether to suppress internal warning
      suppressWarning?: boolean;
    
      // when the first validation rule generates an error stop processed
      first?: boolean;
    
      // when the first validation rule of the specified field generates an error stop the field processed, 'true' means all fields.
      firstFields?: boolean | string[];
    
      messages?: Partial<ValidateMessages>;
    
      /** The name of rules need to be trigger. Will validate all rules if leave empty */
      keys?: string[];
    
      error?: (rule: InternalRuleItem, message: string) => ValidateError;
    }
    
    export type SyncErrorType = Error | string;
    export type SyncValidateResult = boolean | SyncErrorType | SyncErrorType[];
    export type ValidateResult = void | Promise<void> | SyncValidateResult;
    
    export interface RuleItem {
      type?: RuleType; // default type is 'string'
      required?: boolean;
      pattern?: RegExp | string;
      min?: number; // Range of type 'string' and 'array'
      max?: number; // Range of type 'string' and 'array'
      len?: number; // Length of type 'string' and 'array'
      enum?: Array<string | number | boolean | null | undefined>; // possible values of type 'enum'
      whitespace?: boolean;
      trigger?: string | string[];
      fields?: Record<string, Rule>; // ignore when without required
      options?: ValidateOption;
      defaultField?: Rule; // 'object' or 'array' containing validation rules
      transform?: (value: Value) => Value;
      message?: string | ((a?: string) => string);
      asyncValidator?: (
        rule: InternalRuleItem,
        value: Value,
        callback: (error?: string | Error) => void,
        source: Values,
        options: ValidateOption,
      ) => void | Promise<void>;
      validator?: (
        rule: InternalRuleItem,
        value: Value,
        callback: (error?: string | Error) => void,
        source: Values,
        options: ValidateOption,
      ) => SyncValidateResult | void;
    }
    
    export type Rule = RuleItem | RuleItem[];
    
    export type Rules = Record<string, Rule>;
    
    /**
     *  Rule for validating a value exists in an enumerable list.
     *
     *  @param rule The validation rule.
     *  @param value The value of the field on the source object.
     *  @param source The source object being validated.
     *  @param errors An array of errors that this rule may add
     *  validation errors to.
     *  @param options The validation options.
     *  @param options.messages The validation messages.
     *  @param type Rule type
     */
    export type ExecuteRule = (
      rule: InternalRuleItem,
      value: Value,
      source: Values,
      errors: string[],
      options: ValidateOption,
      type?: string,
    ) => void;
    
    /**
     *  Performs validation for any type.
     *
     *  @param rule The validation rule.
     *  @param value The value of the field on the source object.
     *  @param callback The callback function.
     *  @param source The source object being validated.
     *  @param options The validation options.
     *  @param options.messages The validation messages.
     */
    export type ExecuteValidator = (
      rule: InternalRuleItem,
      value: Value,
      callback: (error?: string[]) => void,
      source: Values,
      options: ValidateOption,
    ) => void;
    
    // >>>>> Message
    type ValidateMessage<T extends any[] = unknown[]> =
      | string
      | ((...args: T) => string);
    type FullField = string | undefined;
    type EnumString = string | undefined;
    type Pattern = string | RegExp | undefined;
    type Range = number | undefined;
    type Type = string | undefined;
    
    export interface ValidateMessages {
      default?: ValidateMessage;
      required?: ValidateMessage<[FullField]>;
      enum?: ValidateMessage<[FullField, EnumString]>;
      whitespace?: ValidateMessage<[FullField]>;
      date?: {
        format?: ValidateMessage;
        parse?: ValidateMessage;
        invalid?: ValidateMessage;
      };
      types?: {
        string?: ValidateMessage<[FullField, Type]>;
        method?: ValidateMessage<[FullField, Type]>;
        array?: ValidateMessage<[FullField, Type]>;
        object?: ValidateMessage<[FullField, Type]>;
        number?: ValidateMessage<[FullField, Type]>;
        date?: ValidateMessage<[FullField, Type]>;
        boolean?: ValidateMessage<[FullField, Type]>;
        integer?: ValidateMessage<[FullField, Type]>;
        float?: ValidateMessage<[FullField, Type]>;
        regexp?: ValidateMessage<[FullField, Type]>;
        email?: ValidateMessage<[FullField, Type]>;
        url?: ValidateMessage<[FullField, Type]>;
        hex?: ValidateMessage<[FullField, Type]>;
      };
      string?: {
        len?: ValidateMessage<[FullField, Range]>;
        min?: ValidateMessage<[FullField, Range]>;
        max?: ValidateMessage<[FullField, Range]>;
        range?: ValidateMessage<[FullField, Range, Range]>;
      };
      number?: {
        len?: ValidateMessage<[FullField, Range]>;
        min?: ValidateMessage<[FullField, Range]>;
        max?: ValidateMessage<[FullField, Range]>;
        range?: ValidateMessage<[FullField, Range, Range]>;
      };
      array?: {
        len?: ValidateMessage<[FullField, Range]>;
        min?: ValidateMessage<[FullField, Range]>;
        max?: ValidateMessage<[FullField, Range]>;
        range?: ValidateMessage<[FullField, Range, Range]>;
      };
      pattern?: {
        mismatch?: ValidateMessage<[FullField, Value, Pattern]>;
      };
    }
    
    export interface InternalValidateMessages extends ValidateMessages {
      clone: () => InternalValidateMessages;
    }
    
    // >>>>> Values
    export type Value = any;
    export type Values = Record<string, Value>;
    
    // >>>>> Validate
    export interface ValidateError {
      message?: string;
      fieldValue?: Value;
      field?: string;
    }
    
    export type ValidateFieldsError = Record<string, ValidateError[]>;
    
    export type ValidateCallback = (
      errors: ValidateError[] | null,
      fields: ValidateFieldsError | Values,
    ) => void;
    
    export interface RuleValuePackage {
      rule: InternalRuleItem;
      value: Value;
      source: Values;
      field: string;
    }
    
    export interface InternalRuleItem extends Omit<RuleItem, 'validator'> {
      field?: string;
      fullField?: string;
      fullFields?: string[];
      validator?: RuleItem['validator'] | ExecuteValidator;
    }
    

    组件完善

    新建src\components\baseline\form\src\index.vue

    <template>
        <div>
            <el-form>
                <el-form-item v-for="(item, index) in options" :key="index">
                    <component :is="`el-${item.type}`"></component>
                </el-form-item>
            </el-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { PropType } from 'vue'
    import { FormOptions } from './types/types'
    let props = defineProps({
        options: {
            type: Array as PropType<FormOptions[]>,
            required: true,
        },
    })
    </script>
    <style lang="scss" scoped></style>
    

    运行效果如下:


    image.png

    优化:

                <el-form-item
                    :label="item.label"
                    v-for="(item, index) in options"
                    :key="index"
                >
                    <component :is="`el-${item.type}`"></component>
                </el-form-item>
    

    效果如下:


    image.png

    表单本身属性扩展:

            <el-form v-bind="$attrs">
                <el-form-item
                    :label="item.label"
                    v-for="(item, index) in options"
                    :key="index"
                >
                    <component :is="`el-${item.type}`"></component>
                </el-form-item>
            </el-form>
    

    修改src\views\baseline\form\index.vue

    <template>
        <div class="bs-wrapper">
            <bs-form label-width="1rem" :options="options"></bs-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { FormOptions } from '@/components/baseline/form/src/types/types'
    
    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            rules: [
                {
                    required: true,
                    message: '用户名不能为空',
                    trigger: 'blur',
                },
                {
                    min: 2,
                    max: 10,
                    message: '用户名长度在2-10位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
            },
        },
        {
            type: 'input',
            value: '',
            label: '密码',
            rules: [
                {
                    required: true,
                    message: '密码不能为空',
                    trigger: 'blur',
                },
                {
                    min: 6,
                    max: 20,
                    message: '密码长度在6-20位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
            },
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    

    效果如下:


    image.png

    这里需要用到深拷贝,建议使用一个很好地第三方js工具库:

     npm i -S lodash @types/lodash
    

    优化src\components\baseline\form\src\index.vue

    <template>
        <div>
            <!-- validate-on-rule-change="false"不需要一进来就验证 -->
            <el-form
                :validate-on-rule-change="false"
                :model="model"
                :rules="rules"
                v-bind="$attrs"
            >
                <el-form-item
                    :prop="item.prop"
                    :label="item.label"
                    v-for="(item, index) in options"
                    :key="index"
                >
                    <component
                        v-bind="item.attrs"
                        :is="`el-${item.type}`"
                        v-model="model[item.prop!]"
                    ></component>
                </el-form-item>
            </el-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { PropType, ref, onMounted } from 'vue'
    import { FormOptions } from './types/types'
    let props = defineProps({
        options: {
            type: Array as PropType<FormOptions[]>,
            required: true,
        },
    })
    //局部引入,深拷贝
    import cloneDeep from 'lodash/cloneDeep'
    
    const model = ref<any>({})
    const rules = ref<any>({})
    
    onMounted(() => {
        let m: any = {}
        let r: any = {}
        props.options.map((item: FormOptions) => {
            m[item.prop!] = item.value
            r[item.prop!] = item.rules
        })
        model.value = cloneDeep(m)
        rules.value = cloneDeep(r)
        console.log('model', model.value)
        console.log('rules', rules.value)
    })
    </script>
    <style lang="scss" scoped></style>
    

    调整src\views\baseline\form\index.vue

    <template>
        <div class="bs-wrapper">
            <bs-form label-width="1rem" :options="options"></bs-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { FormOptions } from '@/components/baseline/form/src/types/types'
    
    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            prop: 'username',
            rules: [
                {
                    required: true,
                    message: '用户名不能为空',
                    trigger: 'blur',
                },
                {
                    min: 2,
                    max: 10,
                    message: '用户名长度在2-10位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                clearable: true,
            },
        },
        {
            type: 'input',
            value: '',
            label: '密码',
            prop: 'password',
            rules: [
                {
                    required: true,
                    message: '密码不能为空',
                    trigger: 'blur',
                },
                {
                    min: 6,
                    max: 20,
                    message: '密码长度在6-20位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
                clearable: true,
            },
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    

    效果基本完成:


    image.png

    子元素组件

    像select,是存在option子元素的,调整如下:
    修改src\views\baseline\form\index.vue

    <template>
        <div class="bs-wrapper">
            <bs-form label-width="1rem" :options="options"></bs-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { FormOptions } from '@/components/baseline/form/src/types/types'
    
    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            prop: 'username',
            placeholder: '请输入用户名',
            rules: [
                {
                    required: true,
                    message: '用户名不能为空',
                    trigger: 'blur',
                },
                {
                    min: 2,
                    max: 10,
                    message: '用户名长度在2-10位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                clearable: true,
            },
        },
        {
            type: 'input',
            value: '',
            label: '密码',
            prop: 'password',
            placeholder: '请输入6-20位密码',
            rules: [
                {
                    required: true,
                    message: '密码不能为空',
                    trigger: 'blur',
                },
                {
                    min: 6,
                    max: 20,
                    message: '密码长度在6-20位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
                clearable: true,
            },
        },
        {
            type: 'select',
            value: '1',//初始化表单数据
            label: '职位',
            prop: 'role',
            placeholder: '请选择职位',
            rules: [
                {
                    required: true,
                    message: '职位不能为空',
                    trigger: 'blur',
                },
            ],
            children: [
                { type: 'option', label: '经理', value: '1' },
                { type: 'option', label: '主管', value: '2' },
                { type: 'option', label: '员工', value: '3' },
            ],
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    
    

    修改src\components\baseline\form\src\index.vue

    <template>
        <div class="bs-wrapper">
            <bs-form label-width="1rem" :options="options"></bs-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { FormOptions } from '@/components/baseline/form/src/types/types'
    
    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            prop: 'username',
            placeholder: '请输入用户名',
            rules: [
                {
                    required: true,
                    message: '用户名不能为空',
                    trigger: 'blur',
                },
                {
                    min: 2,
                    max: 10,
                    message: '用户名长度在2-10位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                clearable: true,
            },
        },
        {
            type: 'input',
            value: '',
            label: '密码',
            prop: 'password',
            placeholder: '请输入6-20位密码',
            rules: [
                {
                    required: true,
                    message: '密码不能为空',
                    trigger: 'blur',
                },
                {
                    min: 6,
                    max: 20,
                    message: '密码长度在6-20位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
                clearable: true,
            },
        },
        {
            type: 'select',
            value: '1',//初始化表单数据
            label: '职位',
            prop: 'role',
            placeholder: '请选择职位',
            rules: [
                {
                    required: true,
                    message: '职位不能为空',
                    trigger: 'blur',
                },
            ],
            children: [
                { type: 'option', label: '经理', value: '1' },
                { type: 'option', label: '主管', value: '2' },
                { type: 'option', label: '员工', value: '3' },
            ],
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    
    

    效果如下:


    image.png

    style样式增加

    修改src\views\baseline\form\index.vue

    <template>
        <div class="bs-wrapper">
            <bs-form label-width="1rem" :options="options"></bs-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { FormOptions } from '@/components/baseline/form/src/types/types'
    
    let options: FormOptions[] = [
        {
            type: 'input',
            value: '',
            label: '用户名',
            prop: 'username',
            placeholder: '请输入用户名',
            rules: [
                {
                    required: true,
                    message: '用户名不能为空',
                    trigger: 'blur',
                },
                {
                    min: 2,
                    max: 10,
                    message: '用户名长度在2-10位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                clearable: true,
            },
        },
        {
            type: 'input',
            value: '',
            label: '密码',
            prop: 'password',
            placeholder: '请输入6-20位密码',
            rules: [
                {
                    required: true,
                    message: '密码不能为空',
                    trigger: 'blur',
                },
                {
                    min: 6,
                    max: 20,
                    message: '密码长度在6-20位之间',
                    trigger: 'blur',
                },
            ],
            attrs: {
                showPassword: true,
                clearable: true,
            },
        },
        {
            type: 'select',
            value: '1', //初始化表单数据
            label: '职位',
            prop: 'role',
            placeholder: '请选择职位',
            rules: [
                {
                    required: true,
                    message: '职位不能为空',
                    trigger: 'blur',
                },
            ],
            children: [
                { type: 'option', label: '经理', value: '1' },
                { type: 'option', label: '主管', value: '2' },
                { type: 'option', label: '员工', value: '3' },
            ],
            attrs: {
                style: {
                    width: '100%',
                },
            },
        },
        {
            type: 'checkbox-group',
            value: [],
            prop: 'like',
            label: '爱好',
            rules: [
                {
                    required: true,
                    message: '爱好不能为空',
                    trigger: 'blur',
                },
            ],
            children: [
                {
                    type: 'checkbox',
                    label: '足球',
                    value: '1',
                },
                {
                    type: 'checkbox',
                    label: '篮球',
                    value: '1',
                },
                {
                    type: 'checkbox',
                    label: '乒乓球',
                    value: '3',
                },
            ],
        },
        {
            type: 'radio-group',
            value: '',
            prop: 'gender',
            label: '性别',
            rules: [
                {
                    required: true,
                    message: '性别不能为空',
                    trigger: 'blur',
                },
            ],
            children: [
                {
                    type: 'radio',
                    label: '男',
                    value: '1',
                },
                {
                    type: 'radio',
                    label: '女',
                    value: '2',
                },
                {
                    type: 'radio',
                    label: '保密',
                    value: '3',
                },
            ],
        },
    ]
    </script>
    <style lang="scss" scoped></style>
    
    

    修改src\components\baseline\form\src\index.vue

    <template>
        <div>
            <!-- validate-on-rule-change="false"不需要一进来就验证 -->
            <el-form
                ref="form"
                v-if="model"
                :validate-on-rule-change="false"
                :model="model"
                :rules="rules"
                v-bind="$attrs"
            >
                <template v-for="(item, index) in options" :key="index">
                    <el-form-item
                        :prop="item.prop"
                        :label="item.label"
                        v-if="!item.children || !item.children!.length"
                    >
                        <component
                            :placeholder="item.placeholder"
                            v-bind="item.attrs"
                            :is="`el-${item.type}`"
                            v-model="model[item.prop!]"
                        ></component>
                    </el-form-item>
                    <el-form-item
                        :prop="item.prop"
                        :label="item.label"
                        v-if="item.children && item.children.length"
                    >
                        <component
                            v-bind="item.attrs"
                            :placeholder="item.placeholder"
                            :is="`el-${item.type}`"
                            v-model="model[item.prop!]"
                        >
                            <component
                                v-for="(child, i) in item.children"
                                :key="i"
                                :label="child.label"
                                :value="child.value"
                                :is="`el-${child.type}`"
                            ></component>
                        </component>
                    </el-form-item>
                </template>
            </el-form>
        </div>
    </template>
    <script lang="ts" setup>
    import { PropType, ref, onMounted, watch } from 'vue'
    import { FormOptions } from './types/types'
    let props = defineProps({
        options: {
            type: Array as PropType<FormOptions[]>,
            required: true,
        },
    })
    //局部引入,深拷贝
    import cloneDeep from 'lodash/cloneDeep'
    
    const model = ref<any>()
    const rules = ref<any>()
    
    const initForm = () => {
        let m: any = {}
        let r: any = {}
        props.options.map((item: FormOptions) => {
            m[item.prop!] = item.value
            r[item.prop!] = item.rules
        })
        model.value = cloneDeep(m)
        rules.value = cloneDeep(r)
        console.log('model', model.value)
        console.log('rules', rules.value)
    }
    
    onMounted(() => {
        if (props.options && props.options.length) {
            initForm()
        }
    })
    //监听父组件传递进来的options的变化
    watch(
        () => props.options,
        val => {
            initForm()
        },
        { deep: true }
    )
    </script>
    <style lang="scss" scoped></style>
    
    

    效果如下:


    image.png

    相关文章

      网友评论

        本文标题:【Vue3+Vite+TS】13.0 组件十:强大的表单组件(上

        本文链接:https://www.haomeiwen.com/subject/wcogqrtx.html