在系统中,表单作为用户与后端交互的重要传递组件使用频率极高,故对其进行封装是必然的,也是一个编写规范代码的前端程序员必须做的一件事。
在Vue3中封装组件时,能感受到与Vue2有着很大的不同,故作此记录。
FormItem.tsx
文件是Typescript
中的新特性之一,详细可查阅TS中文文档index.vue
是主体文件type.ts
表单的规约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
},
})
<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 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
是对表单进行渲染的数据项<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>
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
}
一百个人有一百个编写代码的习惯,其上实现是基于模块化的思想,可能看起来有点累,但是我相信能帮助到你。