likes
comments
collection
share

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

作者站长头像
站长
· 阅读数 10

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

代码gitee地址

在线预览

系列文章

# 克隆项目 gitee地址
git clone https://gitee.com/3439/Spurs-Admin.git

# 进入项目目录
cd Spurs-Admin

# 安装依赖
npm install

# 本地开发 启动项目
npm run dev

1.前言

一个管理系统的表单肯定很多,但是很多表单都大同小异,可复用的表单,可以提升实际工作的效率,今天我们就是使用Vue3+Element plus 来实现动态表单,实现思路大致就是通过JSON配置,动态生成表单页面 当然还会有自定义插槽,这样我们碰到特使情况可以灵活使用,以及表单校验

2.目录结构

├── src
│   ├── components          
│   │   └── SpursForm           
│	│			└── formType.ts # 表单类型的配置,每个表单配置,整体表单的配置
│   │       	└── index.vue 	# 表单二次封装的主要业务代码
│   ├── views               # 页面
│   │   ├── simpleForm
│   │   	└── formConfig.ts # 表单配置json
│   │   │   └── index.vue # 表单入口

效果图

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

3.表单类型的配置,每个表单配置,整体表单的配置

具体每个字段的意思可以看字段后面的注释

type FormType =
    |'input'
    | 'password'
    | 'select'
    | 'datepicker'
    | 'timepicker'
    | 'switch'
    | 'radio'
    | 'textarea'
interface ItemOption {
    label: string
    value: string | number
}

export interface FormItem {
    field: string //字段名
    type?: FormType //输入框类型
    label: string //输入框标题
    colSpan?: number// 栅格占据的列数默认24
    disabled?:boolean//表单是否可修改 默认false
    placeholder?: any //输入框默认显示内容
    prop?: string //表单校验
    options?: ItemOption[] //选择器的可选子选项 select
    otherOptions?: any//特殊情况
    isHidden?: boolean
    slotName?: string//处理一些自定义内容
}
export interface FormOption {
    formItems: FormItem[]
    labelWidth?: string//标签的长度
}

4.表单配置json

import {FormOption} from "@/components/SpursForm/formType.ts";
export const formConfig: FormOption = {
    formItems: [
        {
            field: 'id',
            type: 'input',
            label: '用户id',
            placeholder: '请输入用户id',
            colSpan:9,
            prop:"id"
        },
        {
            field: 'account',
            type: 'input',
            label: '用户名',
            disabled:true,
            placeholder: '请输入用户名'
        },
        {
            field: 'realname',
            type: 'input',
            label: '真实姓名',
            placeholder: '请输入真实姓名'
        },
        {
            field: 'cellphone',
            type: 'input',
            label: '电话号码',
            placeholder: '请输入电话号码'
        },
        {
            field: 'enable',
            type: 'select',
            label: '用户状态',
            placeholder: '请选择用户状态',
            options: [
                { label: '启用', value: 1 },
                { label: '禁用', value: 0 }
            ]
        },
        {
            field: 'createAt',
            type: 'datepicker',
            label: '创建时间',
            otherOptions: {
                startPlaceholder: '开始时间',
                endPlaceholder: '结束时间',
                type: 'daterange'
            }
        },
        {
            field: 'special',
            slotName:'special',
            label: '自定义内容',
            colSpan:12
        },
        {
            field: 'special2',
            slotName:'special2',
            label: '自定义内容2',
            colSpan:12
        },
    ],
    labelWidth: '120px'//标签的长度
}

5.表单二次封装的主要业务代码

<template>
  <div class="header">
    <slot name="header"> </slot>
  </div>
  <el-form
      ref="ruleFormRef"
      :label-width="labelWidth"
      status-icon
      :model="modelValue"
      v-bind="$attrs"
  >
    <el-row>
      <template v-for="item in formItems" :key="item.label">
        <el-col :span="item.colSpan??24">
          <el-form-item
              v-if="!item.isHidden"
              :label="item.label"
              :prop="item.field"
          >
            <template v-if="item.type === 'input' || item.type === 'password'">
              <el-input
                  :disabled="item.disabled??false"
                  :placeholder="item.placeholder"
                  :show-password="item.type === 'password'"
                  v-model="modelValue[`${item.field}`]"
                  clearable
              />
            </template>
            <template v-else-if="item.type === 'select'">
              <el-select
                  :placeholder="item.placeholder"
                  v-model="modelValue[`${item.field}`]"
                  style="width: 100%"
                  clearable
              >
                <el-option
                    v-for="option in item.options"
                    :key="option.value"
                    :value="option.value"
                    :label="option.label"
                >
                </el-option>
              </el-select>
            </template>
            <template v-else-if="item.type === 'datepicker'">
              <el-date-picker
                  unlink-panels
                  value-format="YYYY-MM-DD"
                  v-bind="item.otherOptions"
                  v-model="modelValue[`${item.field}`]"
              ></el-date-picker>
            </template>
            <template v-if="item.slotName!=undefined">
              <slot :name="item.slotName"></slot>
            </template>
          </el-form-item>
        </el-col>
      </template>
    </el-row>
    <el-form-item>
      <slot name="footer"></slot>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import {FormItem} from "@/components/SpursForm/formType.ts";
import { ref } from 'vue'
import type { FormInstance } from 'element-plus'

// 定义属性
interface Props {
  formItems: FormItem[] // 表单配置项
  labelWidth?: string // 每个表单标题宽度
  modelValue: object //绑定表单的每个数据
}
const props = withDefaults(defineProps<Props>(), {
  formItems: () => [],
})
const ruleFormRef = ref<FormInstance>()
defineExpose({
  ruleFormRef
})
</script>

获取表单配置json配置的json后通过表单类型去生成不同类型的表单

6.表单入口文件

<template>
  <div class="role-form">
    <spurs-form
        ref="spursFormRef"
        v-bind="formConfig"
        :modelValue="modelValue"
        :rules="rules"
    >
      <template #header>
        <div class="header">
          <h1>我是头部</h1>
        </div>
      </template>
      <template #special>
        <div class="special">
          我是自定义内容
        </div>
      </template>
      <template #special2>
        <div class="special2">
          我是自定义内容2
        </div>
      </template>
      <template #footer>
          <el-button type="primary"  @click="submitForm(spursFormRef.ruleFormRef)">确 定</el-button>
          <el-button  @click="resetClick">取 消</el-button>
      </template>
    </spurs-form>
  </div>
</template>

<script setup lang="ts">
import SpursForm from '@/components/SpursForm/index.vue'
import {onMounted, reactive, ref} from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import {formConfig} from './formConfig'
const modelValue = reactive({
  id: '12',
  account: '用户名1',
  realname: '张三',
})
// console.log(formConfig.formItems[4].options);
onMounted(() => {
  // formConfig.formItems[4].options = [];
  // console.log("222",formConfig);
})
const spursFormRef = ref <any> ()
const rules = reactive<FormRules>({
  id: [
    { required: true, message: '请输入活动名称', trigger: 'blur' },
    { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
  ],
  createAt: [
    { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
  ]
})

const submitForm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  await formEl.validate((valid, fields) => {
    if (valid) {
      console.log('submit!')
    } else {
      console.log('error submit!', fields)
    }
  })
}
// 重置按钮触发
const resetClick = () => {
  formConfig.formItems[4].options = [];
  console.log(formConfig);
  console.log(modelValue);
}

7.表单宽度间距

表单整体的宽度我们引用的时候可以在外层div自己去定义 ,表单每一项的宽度用的是elementplus自带的24三个栅格colSpan?: number// 栅格占据的列数默认24 默认是24用的时候只需在json里面配置就行了colSpan:9,然后页面上

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

这样表单的整体宽度 和每个单项表单的宽度我们都可以灵活布局

8. 表单的数据

表单的数据分两种 一种是页面进来我们需要赋值给表单 一种是 select选择框下拉的options数据等,我们都可以在页面进来是通过后来接口或者自己去定义比如

const modelValue = reactive({
  id: '12',
  account: '用户名1',
  realname: '张三',
});
onMounted(() => {
  // formConfig.formItems[4].options = [];
  //modelValue
  //去改变modelValue或者去改变formConfig的值
})

8.自定义情况的的处理

一个封装如果没有对自定义情况的处理就不是一个好的封装,遇到自定义的情况,在json中定义slotName

{
    field: 'special',
    slotName:'special',
    label: '自定义内容',
    colSpan:12
},

然后页面中去处理自定义的情况

<template v-if="item.slotName!=undefined">
  <slot :name="item.slotName"></slot>
</template>

当然整个表单也定义了 头部和底部的插槽,大家可以自定义去使用

9.继承原生elementPlus表单所有属性,方法,事件

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

比如我们想要用表单的尺寸控制size 属性我们直接使用

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

然后表格就变小了这就是vue3属性透传**v-bind="$attrs"**的强大功能

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

10. 表单校验

elementplus使用的是async-validator表单校验 我们在每个el-form-item去定义prop然后自己去定义rules规则

这里有个需要注意的地方我们的表单在子组件,使用在父组件我们需要把表单给暴露出去

<el-form
    ref="ruleFormRef"
></el-form>
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
</script>
defineExpose({
  ruleFormRef
})

然后使用

<el-button type="primary"  @click="submitForm(spursFormRef.ruleFormRef)">确 定</el-button>
//表单效验
<script setup lang="ts">
    const submitForm = async (formEl: FormInstance | undefined) => {
      if (!formEl) return
      await formEl.validate((valid, fields) => {
        if (valid) {
          console.log('submit!')
        } else {
          console.log('error submit!', fields)
        }
      })
    }
    const rules = reactive<FormRules>({
      id: [
        { required: true, message: '请输入活动名称', trigger: 'blur' },
        { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
      ],
      createAt: [
        { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
      ]
    })
</script>

elementPlus-Form封装动态表单-[从零开始vue3+vite+ts+pinia+router4后台管理(8)

欢迎点赞和收藏 关于二次表单的封装,如果你有更好的想法欢迎评论区交流!感谢

转载自:https://juejin.cn/post/7302356049561255976
评论
请登录