Vue3+ElementPlus 表單組件的封裝實(shí)例
在系統(tǒng)中,表單作為用戶與后端交互的重要傳遞組件使用頻率極高,故對(duì)其進(jìn)行封裝是必然的,也是一個(gè)編寫(xiě)規(guī)范代碼的前端程序員必須做的一件事。
在Vue3中封裝組件時(shí),能感受到與Vue2有著很大的不同,故作此記錄。
form文件夾

FormItem.tsx文件是Typescript中的新特性之一,詳細(xì)可查閱TS中文文檔index.vue是主體文件type.ts表單的規(guī)約
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] || '無(wú)')}</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={'請(qǐng)輸入' + data.label}
autosize = {{
minRows: 3,
maxRows: 4,
}}
{...data.props}
>
</ElInput>
)
// 數(shù)字輸入框
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={'請(qǐng)選擇' + data.label}
{...data.props}
>
{data.options.map((item: any) => {
return <ElOption {...setLabelValue(item, data)} />
})}
</ElSelect>
)
// 單選/區(qū)間日期
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={'請(qǐng)選擇' + data.label}
{...data.props}
/>
)
// 單選/區(qū)間時(shí)間
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()
}
// 表單校驗(yàn)
async function validate() {
? if (!FormRef.value) return
? const result = await FormRef.value.validate()
? return result
}
// 清除表單驗(yàn)證
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
}
}在頁(yè)面中引用

changCarrier.vue是主題頁(yè)面,用來(lái)顯示表單userForm.ts是對(duì)表單進(jìn)行渲染的數(shù)據(jù)項(xiàng)
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">關(guān) 閉</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) //控制表單開(kāi)關(guān)
const title = ref(types.default) //表單標(biāo)題
const FormRef = ref() //表單DOM
const emit = defineEmits(['refresh']) //父組件傳過(guò)來(lái)的方法,作用:在表單提交后觸發(fā),刷新數(shù)據(jù)
defineExpose({ //向父組件暴露其屬性及方法,實(shí)例:父組件點(diǎn)擊添加,觸發(fā)formRef中的addData行為
show,
title,
setData,
addData,
delData,
})
// 表單生成
let formData = useForm()
//新增
function addData() {
handleOpen('add')
}
// 編輯設(shè)置數(shù)據(jù)
function setData(data: object) { //父組件點(diǎn)擊編輯,將值通過(guò)方法傳過(guò)來(lái)
formData.data = reactive({ ...data })
handleOpen('unData')
}
//刪除
async function delData(data: number) {
const res: any = await api.gasSite.deleteQueueApply({
idList: [data],
})
emit('refresh')
}
// 請(qǐng)求
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') //刷新數(shù)據(jù)
show.value = false
} else if (res?.status.state !== '00') {
ElMessage.error(res?.status.state)
}
show.value = false
}
//清除驗(yàn)證信息
async function reset() {
await FormRef.value.resetFields()
}
//新增表單打開(kāi)事件
function handleOpen(type: any) {
formData.formItems = useForm().formItems //表單item
formData.editable = true //打開(kāi)表單編輯
title.value = types[type]
show.value = true //表單的打開(kāi)
}
//表單關(guān)閉事件
function handleClose() {
show.value = false
reset() //重置該表單項(xiàng),將其值重置為初始值,并移除校驗(yàn)結(jié)果
}
// 提交
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:'開(kāi)始時(shí)間',
endPlaceholder:'結(jié)束時(shí)間',
span: 6,
},
{
label: '時(shí)間段范圍',
type: 'time',
prop: 'timeSlot',
format:'HH:mm',
valueFormat:'HH:mm',
start:'開(kāi)始時(shí)間',
end:'結(jié)束時(shí)間',
isRange:true,
span: 6,
},
{
label: '允許排隊(duì)數(shù)量',
type: 'input',
prop: 'queueNum',
span: 6,
},
{
label: '生效類型',
type: 'select',
prop: 'isDelay',
options: [
{
label: '當(dāng)日生效',
value: 0,
},
{
label: '次日生效',
value: 1,
}
],
span: 6,
},
{
label: '生效時(shí)間',
type: 'date',
prop: 'effectiveTime',
format:'YYYY-MM-DD',
valueFormat:'YYYY-MM-DD',
span: 6,
},
],
}),
)
return form
}總結(jié)
一百個(gè)人有一百個(gè)編寫(xiě)代碼的習(xí)慣,其上實(shí)現(xiàn)是基于模塊化的思想,可能看起來(lái)有點(diǎn)累,但是我相信能幫助到你。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue3中ref和reactive的使用場(chǎng)景詳解
這篇文章主要介紹了Vue3中ref和reactive的使用場(chǎng)景,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
vue實(shí)現(xiàn)百度搜索下拉提示功能實(shí)例
這篇文章主要介紹了vue實(shí)現(xiàn)百度搜索下拉提示功能實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
前端vue3中的ref與reactive用法及區(qū)別總結(jié)
這篇文章主要給大家介紹了關(guān)于前端vue3中的ref與reactive用法及區(qū)別的相關(guān)資料,關(guān)于ref及reactive的用法,還是要在開(kāi)發(fā)中多多使用,遇到響應(yīng)式失效問(wèn)題,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-08-08
Vue中.env、.env.development及.env.production文件說(shuō)明
這篇文章主要給大家介紹了關(guān)于Vue中.env、.env.development及.env.production文件說(shuō)明的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-09-09
最適應(yīng)的vue.js的form提交涉及多種插件【推薦】
這篇文章主要介紹了最適應(yīng)的vue.js的form提交涉及多種插件,涉及到 vue.js動(dòng)態(tài)添加css樣式 ,tab切換 ,touch,表單提交,驗(yàn)證,toast,數(shù)據(jù)雙向綁定等。需要的朋友可以參考下2018-08-08

