在系统中,表单作为用户与后端交互的重要传递组件使用频率极高,故对其进行封装是必然的,也是一个编写规范代码的前端程序员必须做的一件事。
在Vue3中封装组件时,能感受到与Vue2有着很大的不同,故作此记录。
form文件夹
FormItem.tsx
文件是Typescript中的新特性之一,详细可查阅TS中文文档index.vue
是主体文件type.ts
表单的规约
FormItem.tsx
import filter from '@/utils/filters' import { ElCheckbox, ElCheckboxGroup, ElDatePicker, ElInput, ElInputNumber, ElOption, ElRadio, ElRadioGroup, ElSelect, ElTimePicker } from 'element-plus' import { defineComponent } from 'vue' // 普通显示 const Span = (form: Record<string, any>, data: Record<string, any>) => ( <span>{data.valueProp ? form[data.valueProp] : (data.filter ? filter(form[data.prop], data.filter) : form[data.prop] || '无')}</span> ) // 输入框 const Input = (form: Record<string, any>, data: Record<string, any>) => ( <ElInput v-model={form[data.prop]} type={data.type} size='small' show-password={data.type == 'password'} clearable placeholder={'请输入' + data.label} autosize = {{ minRows: 3, maxRows: 4, }} {...data.props} > </ElInput> ) // 数字输入框 const InputNumber = (form: Record<string, any>, data: Record<string, any>) => ( <ElInputNumber size='small' v-model={form[data.prop]} controls-position="right" {...data.props} /> ) const setLabelValue = (_item: any, { optionsKey }: any = {}) => { return { label: optionsKey ? _item[optionsKey.label] : _item.label, value: optionsKey ? _item[optionsKey.value] : _item.value, } } // 选择框 const Select = (form: Record<string, any>, data: Record<string, any>) => ( <ElSelect size='small' v-model={form[data.prop]} filterable clearable placeholder={'请选择' + data.label} {...data.props} > {data.options.map((item: any) => { return <ElOption {...setLabelValue(item, data)} /> })} </ElSelect> ) // 单选/区间日期 const Date = (form: Record<string, any>, data: Record<string, any>) => ( <ElDatePicker size='small' v-model={form[data.prop]} type={data.type} value-format={data.valueFormat} format = {data.format} range-separator="至" start-placeholder={data.startPlaceholder} end-placeholder={data.endPlaceholder} placeholder={'请选择' + data.label} {...data.props} /> ) // 单选/区间时间 const Time = (form: Record<string, any>, data: Record<string, any>) => ( <ElTimePicker size='small' v-model={[form[data.prop]]} value-format={data.valueFormat} format = {data.format} range-separator="至" disabled = {form.editable} start-placeholder={data.start} is-range={data.isRange} end-placeholder={data.end} {...data.props} /> ) // 单选 const Radio = (form: Record<string, any>, data: Record<string, any>) => ( <ElRadioGroup v-model={form[data.prop]}> {data.radios.map( (item: { label: string | number | boolean; value: any }) => { return ( <ElRadio label={setLabelValue(item, data.prop).label}> {setLabelValue(item, data.prop).value} </ElRadio> ) }, )} </ElRadioGroup> ) // 多选 const Checkbox = (form: Record<string, any>, data: Record<string, any>) => ( <ElCheckboxGroup size='small' v-model={form[data.prop]}> {data.checkboxs.map( (item: { label: string | number | boolean; value: any }) => { return ( <ElCheckbox label={setLabelValue(item, data.prop).label}> {setLabelValue(item, data.prop).value} </ElCheckbox> ) }, )} </ElCheckboxGroup> ) const setFormItem = ( form: Record<string, any> | undefined, data: Record<string, any>, editable: Boolean, ) => { if (!form) return null if (!editable) return Span(form, data) switch (data.type) { case 'input': return Input(form, data) case 'textarea': return Input(form, data) case 'password': return Input(form, data) case 'inputNumber': return InputNumber(form, data) case 'select': return Select(form, data) case 'date': case 'daterange': return Date(form, data) case 'time': return Time(form, data) case 'radio': return Radio(form, data) case 'checkbox': return Checkbox(form, data) default: return null } } export default () => defineComponent({ props: { data: Object, formData: Object, editable: Boolean, }, setup(props) { return () => props.data ? setFormItem(props.formData, props.data, props.editable) : null }, })
index.vue
<template> <el-form ref="FormRef" :model="prop.data.data" :rules="editable ? prop.data.rules : {}" :inline="inline" :label-position="labelPosition" label-width="atuo"> <el-row :gutter="prop.data.elRowGutter"> <el-col v-for="item in prop.data.formItems" :span="item.span"> <el-form-item :label="item.label ? item.label + ':' : ''" :prop="item.prop" :label-width="item.width"> <FormItem :formData="prop.data.data" :editable="editable" :data="item"> </FormItem> </el-form-item> </el-col> <el-col v-if="btnList && btnList.length" :span="24"> <el-form-item> <template v-for="item in btnList"> <Btn :props="item" @click="onClick(item)"></Btn> </template> </el-form-item> </el-col> </el-row> </el-form> </template>
<script lang="ts" setup> import { computed } from '@vue/reactivity' import type { FormInstance } from 'element-plus' import { ref } from 'vue' import formItem from './FormItem' import type { commonForm } from './type' interface Props { data: commonForm } const prop = defineProps<Props>() const editable = computed(() => !!prop.data?.editable) const inline = computed(() => !!prop.data.formProps?.inline) const labelWidth = computed(() => prop.data.formProps?.labelWidth || '100px') const labelPosition = computed( () => prop.data.formProps?.labelPosition || 'top', ) const btnList = computed(() => { return prop.data.formProps?.btn }) // tsx组件 const FormItem = formItem() const FormRef = ref<FormInstance>() // 表单按钮 function onClick(data: { onClick?: () => void }) { if (!data.onClick) return data.onClick() } // 表单校验 async function validate() { if (!FormRef.value) return const result = await FormRef.value.validate() return result } // 清除表单验证 async function resetFields() { return await FormRef.value.resetFields() } defineExpose({ validate, resetFields, }) </script>
<style scoped> .el-form-item { margin: 0 10px !important; } .el-form-item__label { position: absolute; } .el-form-item__content { width: 100%; padding-left: 80px; } .el-select, .el-input_inner { width: 100%; } </style>
type.ts
type itemType = | 'input' | 'select' | 'switch' | 'radio' | 'date' | 'time' | 'checkbox' | 'daterange' interface FormProps { inline?: Boolean labelWidth?: string | number labelPosition?: 'left' | 'top' | 'right' btn?: object[] } interface FormItems { type: itemType label?: string prop: string valueProp?: string width?: string | number span?: number filter?: string } export class commonForm { public data: any private rules?: object public elRowGutter?: number public editable?: boolean public formProps?: FormProps public formItems: FormItems[] public dataArray?:object[] constructor({ data = {}, rules = {}, editable = true, formProps = {}, formItems = [], elRowGutter = 0, }: any) { this.data = data this.rules = rules this.elRowGutter = elRowGutter this.editable = editable this.formItems = formItems this.formProps = formProps } }
在页面中引用
changCarrier.vue
是主题页面,用来显示表单userForm.ts
是对表单进行渲染的数据项
index.vue
<template> <el-dialog v-model="show" v-if="show" :title="`${title}人员`" :before-close="handleClose" width="60%"> <Form ref="FormRef" :data="formData"></Form> <template #footer> <el-button @click="handleClose">关 闭</el-button> <el-button type="primary" v-show="!isDetail" @click="submit">提 交</el-button> </template> </el-dialog> </template>
<script lang="ts" setup> import { reactive, ref, defineEmits } from 'vue' // import api from '@/api' import { ElMessage } from 'element-plus' import useForm from './hooks/useForm' //表单的 import api from '@/api/index' enum types { 'default' = '', 'add' = '新增', 'unData' = '编辑', 'detail' = '详情', } const show = ref(false) //控制表单开关 const title = ref(types.default) //表单标题 const FormRef = ref() //表单DOM const emit = defineEmits(['refresh']) //父组件传过来的方法,作用:在表单提交后触发,刷新数据 defineExpose({ //向父组件暴露其属性及方法,实例:父组件点击添加,触发formRef中的addData行为 show, title, setData, addData, delData, }) // 表单生成 let formData = useForm() //新增 function addData() { handleOpen('add') } // 编辑设置数据 function setData(data: object) { //父组件点击编辑,将值通过方法传过来 formData.data = reactive({ ...data }) handleOpen('unData') } //删除 async function delData(data: number) { const res: any = await api.gasSite.deleteQueueApply({ idList: [data], }) emit('refresh') } // 请求 async function request() { let res: any // formData是否存在id值, 存在id值表示编辑, 不存在则为添加 if (!formData.data?.id) { //编辑提交 res = await api.gasSite.addQueueApply(formData.data) } else if (formData.data?.id) { //新增提交 res = await api.gasSite.updateStartWarehouse(formData.data) } if (res?.status.state === '00') { ElMessage.success('操作成功') title.value = types.default emit('refresh') //刷新数据 show.value = false } else if (res?.status.state !== '00') { ElMessage.error(res?.status.state) } show.value = false } //清除验证信息 async function reset() { await FormRef.value.resetFields() } //新增表单打开事件 function handleOpen(type: any) { formData.formItems = useForm().formItems //表单item formData.editable = true //打开表单编辑 title.value = types[type] show.value = true //表单的打开 } //表单关闭事件 function handleClose() { show.value = false reset() //重置该表单项,将其值重置为初始值,并移除校验结果 } // 提交 async function submit() { const result = await FormRef.value.validate() if (result) request() } </script>
useForm
import { commonForm } from '@/components/common/form/type' import { reactive } from 'vue' export default () => { const rules = { name: [ { required: true, message: '人员名称', trigger: 'blur' } ] } const form = reactive( new commonForm({ data: [], editable: true, rules: rules, formItems: [ { label: '人员名称', type: 'select', prop: 'name', }, { label: '日期范围', type: 'daterange', prop: 'queueDate', format:'YYYY-MM-DD', valueFormat:'YYYY-MM-DD', startPlaceholder:'开始时间', endPlaceholder:'结束时间', span: 6, }, { label: '时间段范围', type: 'time', prop: 'timeSlot', format:'HH:mm', valueFormat:'HH:mm', start:'开始时间', end:'结束时间', isRange:true, span: 6, }, { label: '允许排队数量', type: 'input', prop: 'queueNum', span: 6, }, { label: '生效类型', type: 'select', prop: 'isDelay', options: [ { label: '当日生效', value: 0, }, { label: '次日生效', value: 1, } ], span: 6, }, { label: '生效时间', type: 'date', prop: 'effectiveTime', format:'YYYY-MM-DD', valueFormat:'YYYY-MM-DD', span: 6, }, ], }), ) return form }
总结
一百个人有一百个编写代码的习惯,其上实现是基于模块化的思想,可能看起来有点累,但是我相信能帮助到你。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持阿兔在线工具。