Vue3+ElementPlus 表单组件的封装实例

目录
  • form文件夹
  • FormItem.tsx
  • 在页面中引用
  • 总结

在系统中,表单作为用户与后端交互的重要传递组件使用频率极高,故对其进行封装是必然的,也是一个编写规范代码的前端程序员必须做的一件事。

在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
}

总结

一百个人有一百个编写代码的习惯,其上实现是基于模块化的思想,可能看起来有点累,但是我相信能帮助到你。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Vue结合Element-Plus封装递归组件实现目录示例

    目录 前言 用正则匹配出所有的h标签并且保存在数组中 封装函数,将数组中的内容变成父子结构 封装递归组件fold-item(在使用之前不要忘了导入自己哦) 在foldMenu中使用递归组件 使用效果 前言 在写我的个人博客网站,用MarkDownIt将md解析成html时,我一直在想,怎么才能实现官方文档他们那些可折叠的目录结构呢?我有那么多标题(h1...h5),而且有的文章是只有h2或者h3的,难道我要在目录组件里面一个个v-if来渲染这些标题达到目录的效果嘛?这个问题在我某一天看vue文档

  • Vue3+Vite+TS实现二次封装element-plus业务组件sfasga

    目录 1.结构字符串 2.返回tuple元组 3.访问Dict字典 4.运用库 5.在列表中切片/步进 6.用 ranges 1.结构字符串 你会经常需求打印字符串.要是有很多变量,防止下面这样: name = "Raymond" age = 22 born_in = "Oakland, CA" string = "Hello my name is " + name + "and I'm " + str(age) + &quo

  • vite+vue3+element-plus项目搭建的方法步骤

    使用vite搭建vue3项目 通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目. $ npm init vite-app <project-name> $ cd <project-name> $ npm install $ npm run dev 引入Element Plus 安装Element Plus: npm install element-plus --save main.js中完整引入 Element Plus: import { createApp

  • Vue3+ElementPlus 表单组件的封装实例

    目录 form文件夹 FormItem.tsx 在页面中引用 总结 在系统中,表单作为用户与后端交互的重要传递组件使用频率极高,故对其进行封装是必然的,也是一个编写规范代码的前端程序员必须做的一件事. 在Vue3中封装组件时,能感受到与Vue2有着很大的不同,故作此记录. form文件夹 FormItem.tsx文件是Typescript中的新特性之一,详细可查阅TS中文文档 index.vue是主体文件 type.ts表单的规约 FormItem.tsx import filter from

  • YII2.0之Activeform表单组件用法实例

    本文实例讲述了YII2.0之Activeform表单组件用法.分享给大家供大家参考,具体如下: Activeform 文本框:textInput(); 密码框:passwordInput(); 单选框:radio(),radioList(); 复选框:checkbox(),checkboxList(); 下拉框:dropDownList(); 隐藏域:hiddenInput(); 文本域:textarea(['rows'=>3]); 文件上传:fileInput(); 提交按钮:submitBu

  • vue封装form表单组件拒绝重复写form表单

    目录 前言 核心思想: 实现重点: 表单双向绑定的方式有两种: 1.使用v-model进行双向绑定 2.使用v-model的语法糖 配置项 整体字段: 效果浏览 源码放送 1. baseForm组件 2. 父组件 3. 配置项 4. 添加或编辑 前言 在日常工作中,当需要处理的form表单很多时,我们没有必要一遍又一遍地重复写form表单,直接封装一个组件去处理就好.其实很早之前就有涉猎通过使用类似配置json方法写form表单的文章,虽然当时也没怎么认真看...我们前端组也是使用这种思想配置的

  • jQuery Mobile框架中的表单组件基础使用教程

    一.表单组件基础 1.组件简介 jQuery Mobile 中的表单组件是基于标准 HTML ,然后在此基础上增强样式,因此即使浏览器不支持 jQuery Mobile 表单仍可正常使用.需要注意的是, jQuery Mobile 会把表单元素增强为触摸设备很容易使用的形式,因此对于 iphone/ipad 与 Android 使用 Web 表单将会变得非常方便. jQuery Mobile 的表单组件有以下几种: (1)文本输入框, type="text" 标记的 input 元素会

  • 解决layui的使用以及针对select、radio等表单组件不显示的问题

    layui是国内一款界面比较整洁大方的ui框架,里面封装了很多前端开发常用的组件,通常我们直接复制代码过去就可以实现效果,可以提高我们的开发效率. 使用步骤: 1.下载layui:https://www.layui.com 2.将layui文件夹复制到自己的项目里: 3.在html页面引入相关的css和js: <link rel="stylesheet" type="text/css" href="/layui/css/layui.css"

  • 关于element的表单组件整理笔记

    element表单及代码的展示 详细可以看element表单官方网址 结构.功能分析 通过介绍以及源码可以发现,表单是具有收集.校验.提交数据三个功能的. 他的基本结构如下: <el-form :model="ruleForm" :rules="rules" ref="ruleForm"> <el-form-item label="账号" prop="name"> <el-in

  • 为JQuery EasyUI 表单组件增加焦点切换功能的方法

    1.背景说明 在使用 JQuery  EasyUI 各表单组件时,实际客户端页面元素是由 JQuery EasyUI 生成的,元素的焦点切换,虽然 Tab 键可以正常用,但顺序控制属性 tabindex 不起作用,因为页面看到的元素,是生成的,没有tabindex 属性,而真实的元素被隐藏了.本文通过一个自定义函数,实现Tab 和 回车键的焦点切换功能. 2.函数定义 通过捕获窗口按件,对回车和Tab键进行了热点切换处理.先根据当前焦点,获取需要tabindex 属性,加1后为下一焦点无素的属性

  • Bootstrap表单组件教程详解

    表单常见的元素主要包括:文本输入框.下拉选择框.单选框.复选框.文本域.按钮等.下面是不同的bootstrap版本: LESS: forms.less SASS: _forms.scss bootstrap仅对表单内的fieldset.legend.label标签进行了定制 fieldset { min-width: 0; padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; padding: 0; m

  • 微信小程序form表单组件示例代码

    表单,将组件内的用户输入的<switch/> <input/> <checkbox/> <slider/> <radio/> <picker/> 提交. 当点击<form/>表单中 formType 为 submit 的<button/>组件时,会将表单组件中的 value 值进行提交,需要在表单组件中加上 name 来作为 key. 属性名 类型 说明 report-submit Boolean 是否返回fo

  • 微信小程序防止多次点击跳转和防止表单组件输入内容多次验证功能(函数防抖)

    一.函数节流(throttle) **函数节流:一个函数执行一次后,只有大于设定的执行周期后才会执行第二次**.有个需要频繁触发函数,出于优化性能角度,在规定时间内,只让函数触发的第一次生效,后面不生效. ### 1.如何实现 其原理是用时间戳来判断是否已到回调该执行时间,记录上次执行的时间戳,然后每次触发 scroll 事件执行回调,回调中判断当前时间戳距离上次执行时间戳的间隔是否已经到达 规定时间段,如果是,则执行,并更新上次执行的时间戳,如此循环: function throttle(fn

随机推荐