基于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,large
            icon: '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) => (
    <ElInput
      v-model={form[data.field]}
      type={data.type}
      size={data.size}
      show-password={data.type == 'password'}
      clearable
      placeholder={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) => (
    <ElSelect
      size={data.size}
      v-model={form[data.field]}
      filterable
      clearable 
      placeholder={data.placeholder}
      {...data.props}
    >
      {data.options.map((item) => {
        return <ElOption {...setLabelValue(item, data)} />
      })}
    </ElSelect>
  )

  const Button = (form, data) =>{
    <ElButton
        type={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 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)
      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?<ElButton
        type={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" }); //类型zip
    caseCronjob.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>

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
你可以尝试以下步骤来封装一个Vue 3和TypeScript下使用Element Plus的表单提交组件: 1. 安装必要的依赖: - Vue 3:`npm install vue@next` - TypeScript:`npm install -D typescript` - Element Plus:`npm install element-plus` 2. 创建一个新的Vue组件,并为其选择一个合适的名称,例如`FormSubmit.vue`。 3. 在`FormSubmit.vue`文件,导入必要的模块和样式: ```vue <template> <!-- 表单内容 --> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { ElButton, ElForm, ElFormItem } from 'element-plus'; export default defineComponent({ components: { ElButton, ElForm, ElFormItem, }, }); </script> <style scoped> /* Element Plus 样式 */ @import 'element-plus/packages/theme-chalk/src/index.scss'; /* 自定义样式 */ /* ... */ </style> ``` 4. 在模板编写表单内容,并使用Element Plus的组件来构建表单: ```vue <template> <el-form ref="form" :model="formData" label-width="120px"> <el-form-item label="姓名" prop="name"> <el-input v-model="formData.name"></el-input> </el-form-item> <!-- 更多表单项 --> <el-form-item> <el-button type="primary" @click="submitForm">提交</el-button> </el-form-item> </el-form> </template> <script lang="ts"> // ... export default defineComponent({ // ... data() { return { formData: { name: '', // 更多表单字段 } }; }, methods: { submitForm() { // 表单提交逻辑 if (this.$refs.form.validate()) { // 表单验证通过,执行提交操作 // ... } } } }); </script> ``` 这样,你就可以使用封装好的表单提交组件来方便地处理表单提交了。你可以根据实际需求添加更多的表单项,并在`submitForm`方法实现你的提交逻辑。希望这可以帮到你!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值