> 文章列表 > 基于vue3+Elementplus封装通用表单组件

基于vue3+Elementplus封装通用表单组件

基于vue3+Elementplus封装通用表单组件

主要思路:基于elementplus ,并利用配置文件,生成表单控件(el-input,el-select,el-button等),设置栏栅布局,设置表单校验,提交按钮,placeholder,labelWidth,elRowGutter,labelPosition,slot插槽个性化内容等。

1.相关文件:

  1. testCaseConfig.js:配置表单控件的数据,按钮,校验数据等;
  2. FormItem.jsx:生成表单控件el-input,el-select,el-button等)
  3. FormButton.jsx:生成按钮
  4. TForm.vue:通用表单组件
  5. GenerateTestCase.vue:页面显示

2.config.js

  • rules表单校验rules
  • formItems生成表单控件的数据
  • buttons需要生成的按钮
  • elRowGutter每个单元格之间的间隔
  • tableBorder表单是否需要边框 
  • colLayout栏栅布局配置(没用到)
// Object.freeze是可以冻结对象,对于不需要改变的对象使用,可以提升性能
const testCaseConfig = {rules: {sitFunctionName: [{ required: true, message: '请输入SIT功能列名', trigger: 'blur' }],sitTestProject: [{ required: true, message: '请输入SIT测试项目', trigger: 'blur' }],sitProductionTaskNumber: [{ required: true, message: '请输入SIT生产任务编号', trigger: 'blur' }],sitBatch: [{ required: true, message: '请输入SIT批次', trigger: 'blur' }],stExperimentalArchives: [{ required: true, message: '请输入ST实验档案', trigger: 'blur' }],stAcceptancePerson: [{ required: true, message: '请选择ST验收人员', trigger: 'change' }],},formItems: [{field: 'sitFunctionName',prop: 'sitFunctionName',label: 'SIT功能',placeholder: 'SIT功能',type: 'input',// size: 'small',span: 8,},{field: 'sitTestProject',prop: 'sitTestProject',type: 'input',label: 'SIT测试项目',placeholder: 'SIT测试项目',// editable: true,// size: 'small',span: 8,},{field: 'sitProductionTaskNumber',prop: 'sitProductionTaskNumber',type: 'input',label: 'SIT生产任务编号',labelWidth: '150px',placeholder: 'SIT生产任务编号',isHidden: false,span: 8,},{field: 'sitBatch',prop: 'sitBatch',type: 'input',label: 'SIT批次',placeholder: 'SIT批次',span: 8,},{field: 'stExperimentalArchives',prop: 'stExperimentalArchives',type: 'input',label: 'ST试验档案',span: 8,placeholder: 'ST试验档案',},{field: 'stAcceptancePerson',prop: 'stAcceptancePerson',type: 'select',label: 'ST验收人员',span: 8,labelWidth: '150px',placeholder: '请选择ST验收人员',options: []}],// 按钮buttons: [{name: '生成案例',title: 'generateTestCase',type: 'primary',size: 'default', //可以是default,small,largeicon: 'Edit',// 按钮是否为朴素类型// plain: true,onClick: null}, {name: '重置',type: 'info',title: 'resetTestCase',size: 'default',icon: 'DocumentDelete',// plain: true,onClick: null},{name: '下载测试案例',type: 'success',title: 'download',size: 'default',icon: 'Download',isHidden: true,// plain: true,onClick: null}],ref: 'testCaseFormRef',labelWidth: '120px',labelPosition: 'right',inline: true,editable: true,// 单元列之间的间隔elRowGutter: 10,// size: 'small',// 是否需要form边框tableBorder: true,colLayout: {xl: 5, //2K屏等lg: 8, //大屏幕,如大桌面显示器md: 12, //中等屏幕,如桌面显示器sm: 24, //小屏幕,如平板xs: 24 //超小屏,如手机}
}export default testCaseConfig;

3.FormItem.jsx

import {ElInput,ElSelect,ElOption,ElButton} from 'element-plus'import { defineComponent } from 'vue'// 普通显示
const Span = (form, data) => (<span>{data}</span>)// 输入框
const Input = (form, data) => (<ElInputv-model={form[data.field]}type={data.type}size={data.size}show-password={data.type == 'password'}clearableplaceholder={data.placeholder}autosize = {{minRows: 3,maxRows: 4,}}{...data.props}></ElInput>)const setLabelValue = (_item, { optionsKey } = {}) => {return {label: optionsKey ? _item[optionsKey.label] : _item.label,value: optionsKey ? _item[optionsKey.value] : _item.value,}}// 选择框const Select = (form, data) => (<ElSelectsize={data.size}v-model={form[data.field]}filterableclearable placeholder={data.placeholder}{...data.props}>{data.options.map((item) => {return <ElOption {...setLabelValue(item, data)} />})}</ElSelect>)const Button = (form, data) =>{<ElButtontype={data.type}size={data.size}icon={data.icon}plain={data.plain}click={data.clickBtn}value={data.value}></ElButton>}const setFormItem = (form,data,editable,) => {if (!form) return nullif (!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)case 'button':return Button(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},})

4.FormButton.jsx

 emits:['click']需要声明,否则有警告,但是声明了原生的click会被覆盖,所以没有声明

import {ElButton} from 'element-plus'
import { defineComponent } from 'vue'const Button = (form, data) =>(!data.isHidden?<ElButtontype={data.type}size={data.size}icon={data.icon}plain={data.plain}click={data.onClick}>{data.name}</ElButton>:'')const setBottonItem = (form,data,editable,) => {if(!data) return null;if (!editable) return Span(form, data)return Button(form, data);}export default () =>
defineComponent({props: {data: Object,formData: Object,editable: Boolean,},// 这里必须要声明// emits:['click'],setup(props) {return () =>props.data? setBottonItem(props.formData, props.data, props.editable): null},
})

5.

<template><div class="testcase-box" v-loading="loading"><t-form ref="testCaseFormRef":btnList="configData.buttons":modelForm="testCaseForm":formBorder="true":rules="configData.rules":data="formItems"><template #footer v-if="testcasePath"><div class="file-path">文件路径:{{ testcasePath }}</div></template></t-form ></div></template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
import { getSTAndSITCase, downloadTestCase, getUsersExceptSelfAndVip } from '@/api/api'import TForm from '@/components/form/TForm.vue'
import testCaseConfig from '@/config/form/testCaseConfig'
import { addFormOptions } from '@/tools/tools'const configData = ref([]);
configData.value = testCaseConfig;
// data为必填项
const formItems = configData.value.formItems ? configData.value.formItems : {};const loading = ref(false);
const testCaseFormRef = ref();
const allUser = ref([]);
const testcasePath = ref('');const testCaseForm = reactive({sitFunctionName: '',  //SIT功能列名sitTestProject: '',   //SIT测试项目sitProductionTaskNumber: '',   //SIT生产任务编号sitBatch: '',   //SIT批次stExperimentalArchives: '',    //ST试验档案stAcceptancePerson: ''   //ST验收人员
});const getStAcceptancePerson = async () => {let res = await getUsersExceptSelfAndVip();if (res.data.code === 200) {allUser.value = res.data.data;// 设置ST验收人员 选择框选项let tempOptions = addFormOptions(allUser.value);configData.value.formItems[5].options = tempOptions;} else {ElMessage.error("获取验收人员名单出错:" + res.data.errorMsg);}
}
getStAcceptancePerson();const generateTestCase = async () => {if (!testCaseFormRef.value) return;const result = await testCaseFormRef.value.validate()if(result){loading.value = true;let res = await getSTAndSITCase(testCaseForm);if (res.data.code === 200) {loading.value = false;testcasePath.value = res.data.data;configData.value.buttons[2].isHidden = false;ElMessage.success("案例生成成功");} else {loading.value = false;ElMessage.error("案例生成失败:" + res.data.errorMsg);}}
}const download = async () => {loading.value = true;let result = await downloadTestCase({ filePathAndName: testcasePath.value });if (result.data.type == 'application/json') {const reader = new FileReader();reader.readAsText(result.data, 'utf-8');reader.onload = function () {const { code, errorMsg } = JSON.parse(reader.result);//reader.result里面含报错信息ElMessage.error("案例下载失败:" + errorMsg);}} else if (result.data.type == 'application/octet-stream') {const caseCronjob = document.createElement("a");let blobCronjob = new Blob([result.data], { type: "application/zip, application/x-zip-compressed" }); //类型zipcaseCronjob.style.display = "none";caseCronjob.href = URL.createObjectURL(blobCronjob);let tempArr = testcasePath.value.split("\\\\");let fileName = tempArr[tempArr.length-1];// download属性,加上download后会指示浏览器下载而不是导航。但是这个属性是HTML5属性,仅兼容版本较高的浏览器caseCronjob.setAttribute("download", fileName);document.body.appendChild(caseCronjob);caseCronjob.click();document.body.removeChild(caseCronjob);} else {ElMessage.error("案例下载失败:下载数据类型有误");}loading.value = false;
}const resetTestCase = () => {if (!testCaseFormRef.value) return;testCaseFormRef.value.resetFields();
}/* 动态设置config中buttons 的点击方法*/
configData.value.buttons.forEach((item,index)=>{// 顺序必须和config中buttons顺序一致[generateTestCase,resetTestCase,download].map((k,v)=>{if(index === v) item.onClick = k;});
});</script>
<style scoped>
.testcase-box{width:100%;height: 100%;
}
.file-path{margin: 0 auto;
}
</style>

州安房产网