Vue3+ts+element-plus 组件的二次封装-- 新增修改Form弹框的二次封装,通过JSON进行配置Form表单,实现高内聚低耦合

Vue 笔记

本人是一个web前端开发工程师,主要是vue框架,整理了一些Vue常用的技术,一方面是分享,一方面是做总结,今后也会一直更新,有好建议的同学欢迎评论区分享 ;-)

Vue专栏:点击此处
Vue组件库专栏:点击此处
Vue2 vs Vue3 专栏:点击此处
Typescript专栏:点击此处

Vue3+ts+element-plus 组件的二次封装-- 新增修改Form弹框的二次封装,通过JSON进行配置Form表单,实现高内聚低耦合_第1张图片


组件库开发流程

Vue组件库专栏会按顺序执行一下流程,不断完善组件库开发流程

  1. Vue3+element-plus+vite 组件的二次封装,封装了头部的搜索条件栏,tabel栏,分页栏,form表单,都设置成了通过json可配置项,方便复用;
  2. 封装好了就开始打包,并且进行本地测试;
  3. 组件库发布到npm;
  4. 添加vitest单元测试框架;
  5. 添加vuepress文档。

文章目录

  • Vue 笔记
  • 组件库开发流程
  • 前言
  • 需求分析
  • Form弹框的二次封装
    • 1. 效果图
    • 2. 父组件
      • 2.1 父组件 template 中的调用
      • 2.1 父组件 script 中的调用
        • 2.1.1 新增以及修改按钮
        • 2.1.2 监听到@confirm事件
        • 2.1.3 配置项
    • 3. 组件的封装
      • 3.1 封装组件的template
        • 3.1.1 el-dialog
        • 3.1.2 el-form
        • 3.1.3 el-form-item
      • 3.2 封装组件的script
        • 3.2.1 props的定义
        • 3.2.2 监听事件
        • 3.2.3 确定按钮
  • 总结
  • 组件源码


前言

环境状态
vue版本:vue3
是否使用 ts:是

后台管理系统的网站,一个页面无非就是4个常用业务块

  1. 头部的搜索栏
  2. table表格
  3. 页脚
  4. 新增编辑弹框

那咋们是不是可以将其进行封装成组件呢?
只需要传入一个配置文件就可以了~

项目解构如下:
在这里插入图片描述

封装后的展示图如下:
在这里插入图片描述

效果图如下:

在这里插入图片描述

如果能做成这样,是不是页面就会整洁很多?


需求分析

一个管理系统的新增跟修改弹框,基本上是样式是一致的,不外乎就是弹出了一个Dialog,里面有一个form表单,表单里面有产品需要的一些字段展示,点击确定,验证了没问题,就向后端发起请求。

那要封装的组件需要什么参数呢:

  1. title:弹框的标题。
  2. formConfig:配置文件,用来告诉组件,我有什么formItem,然后每个formItem是要绑定哪一个属性prop,以及根据type确定是什么组件之类的。
  3. formData:传递给组件的一些原始数据,新增的时候可以传递空对象{},修改的时候,可以将修改的一个对象传递给组件。
  4. visible:用来设置隐藏还是显示弹框。
  5. @confirm:弹框点击确定,校验没问题,会触发的事件,并将form表单传递给父组件,实现业务层的脱离。

当然还有其他的配置项,按自身需求自己添加即可...


Form弹框的二次封装

1. 效果图

Vue3+ts+element-plus 组件的二次封装-- 新增修改Form弹框的二次封装,通过JSON进行配置Form表单,实现高内聚低耦合_第2张图片


2. 父组件

2.1 父组件 template 中的调用


<PenkForm
  :title="title"
  :formConfig="formConfig"
  :formData="formData"
  v-model:visible="visible"
  append-to-body
  @confirm="saveItem"
>

上述的主要配置有了,还有一些额外的,主要用于传递给elementUI的一些配置,在后面的组件interface接口也有详细的描述


2.1 父组件 script 中的调用

2.1.1 新增以及修改按钮

主要是将弹框打开,并且传递formData,新建的时候传空对象,修改传递row,即后台返回的对象。

// 添加数据按钮
function handleAddItem() {
  visible.value = true;
  title.value='新增';
  formData = reactive({});
}

// 修改数据按钮
function handleEditItem(index: number, row: any) {
  visible.value = true;
  title.value='编辑';
  formData = reactive(row);
}

2.1.2 监听到@confirm事件

监听二次封装的弹框,如果有@confirm 事件,就说明表单内校验没问题,并且第一个参数携带一个新的formData对象,如果该对象有id,就证明是修改数据;没有id,证明是新增 的数据。

// 新增,编辑数据
function saveItem(form: any) {
  if (form.id) {
    http.update(form).then((res: any) => {
      getList();
      ElMessage({
        type: "success",
        message: "修改成功!",
      });
      visible.value = false;
    });
  } else {
    http.add(form).then((res: any) => {
      getList();
      ElMessage({
        type: "info",
        message: "添加成功!",
      });
      visible.value = false;
    });
  }
}

2.1.3 配置项
const formConfig = {
  formItemConfig: [
    {
      label: "类型名",
      prop: "typeName",
      type: "input",
    },
    {
      label: "父级类型名",
      prop: "parentId",
      type: "select",
      data: [],
    },
  ],
  rules: {
    typeName: [
      {
        required: true,
        message: "请输入类型名",
        trigger: "blur",
      },
    ],
  },
};

3. 组件的封装

3.1 封装组件的template

3.1.1 el-dialog
  1. 多个属性都是由父组件通过props传递给了封装组件,再由封装组件到el-dialog组件。
    父组件=> 封装组件=> elementUI组件
  2. 双向数据绑定则通过了一个临时变量visible1,通过watch监听visible1,再触发事件,实现封装组件跟父组件的双向绑定.
    elementUI 组件=> 封装组件=> watch触发update => 父组件
// 子组件的template
<el-dialog
  v-model="visible1"
  :fullscreen="props.fullscreen"
  :draggable="props.draggable"
  :title="props.title"
  :width="props.width||'50%'"
  :before-close="handleClose"
>

3.1.2 el-form
  1. form表单是封装组件里面的变量,后期通过事件,再将form传递给父组件,这样就隔离了业务层。
  2. rules校验字段是由父组件传递过来的。
<el-form
  ref="ruleFormRef"
  label-width="120px"
  size="large"
  :model="form"
  :rules="props.formConfig.rules"
>

3.1.3 el-form-item

部分代码讲解,可以先看代码,看不懂再看…

  1. template 的for循环,主要用于解析有多少form-item
  2. el-form-item 的prop 的属性!!! 用于绑定这个form-item对应的是什么属性名,用于rules校验
  3. el-input 的v-if 的 item.type 主要用来判断用什么elementUI组件
  4. el-input 的v-model 主要用来绑定数据,对象是form对象,字段名是配置文件传递的字段名formItemConfig[x].prop,主要运用了字符串索引
  5. 其他的都是通用的配置项…
<template
  v-for="item in props.formConfig.formItemConfig"
  :key="item.prop"
>
  <el-form-item
    :label="item.label + ':'"
    :style="{ display: item.hidden == true ? 'none' : '' }"
    :prop="item.prop"
  >
    
    <el-input
      v-if="item.type == 'input'"
      v-model="form[item.prop]"
      :placeholder="item.placeholder || ''"
      :clearable="item.clearable"
      :disabled="item.disabled"
      :type="item.inputType"
      :row="item.row"
      :style="{ width: item.width + 'px' }"
    />

3.2 封装组件的script

下边注释很详细了,一般是给调用者看的,如果看不懂,估计还需多多努力~...

3.2.1 props的定义

这边虽说是Form弹框,但是还是用到了elementUI的两个组件:
1个是el-dialog,1个是el-form。

// 单个form item 的属性
interface formItemConfig {
  // 必填
  // 标签名
  label: string;
  // 对应的属性名 如: from.name ~
  prop: string;
  // 组件所需类型,下拉框或者输入框
  type: string;

  // 选填-通用
  // 宽度
  width?: number;
  // 占位符
  placeholder?: string;
  // 是否清空
  clearable?: boolean;
  // 是否使能
  disabled?: boolean;
  // 是否隱藏
  hidden?: boolean;

  // 选填
  // 特殊组件参数
  // 输入框判断是否文本域
  inputType?: string;
  // 输入框为文本域的时候的行数
  row: number;

  // 选填-特殊
  // 数据,一般是下拉框之类需要可选项的才用到
  data?: any;
}

interface formConfig {
  // 每一项的配置
  formItemConfig: formItemConfig;
  // 规则
  // https://element-plus.gitee.io/zh-CN/component/form.html#自定义校验规则
  rules: any;
}

interface props {
  // 以下是el-form  的配置
  // 编辑时候的formData数据
  formData: any;
  // form配置 包括form item 以及 rules
  formConfig: formConfig;

  // 以下是el-dialog 的配置
  visible: boolean;
  // dialog配置
  width:string|number;
  // 弹框标题
  title: string;
  // 是否为全屏 Dialog
  fullscreen?: boolean;
  // 为 Dialog 启用可拖拽功能
  draggable?: boolean;
  // Dialog 自身是否插入至 body 元素上。 嵌套的 Dialog 必须指定该属性并赋值为 true
  "append-to-body"?: boolean;
  // 是否可以通过点击 modal 关闭 Dialog
  "close-on-click-modal"?: boolean;
}

const props = defineProps<props>();

3.2.2 监听事件
// 生成事件对象,数组中就是对象名
const emit = defineEmits(["update:visible", "confirm"]);

// 监听父组件的visible,用来简介控制el-dialog的弹框开关,一般是用于开
watch(
  () => props.visible,
  (n, o) => {
    visible1.value = n;
  }
);
// 监听el-dialog显示状态,再通过@update:visible 通知父组件,一般是用于关
watch(visible1, (n, o) => {
  emit("update:visible", n);
});

// 每次触发,就证明父组件点了修改或者添加的按钮,传递了一个新的formData
// 需要重新给form 赋值,并且,对该表单项进行重置
watch(
  () => props.formData,
  (n, o) => {
    resetForm(ruleFormRef.value)
    form = reactive(n);
  }
);

// 重置表单
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.resetFields();
};


3.2.3 确定按钮

先对其进行校验,通过再出发@confirm事件

// 确定按钮触发
const confirm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      emit("confirm", form);
    } else {
      console.log("error submit!", fields);
      ElMessage({
        showClose: true,
        message: "请完善表单信息!",
        type: "error",
      });
    }
  });
};

总结

这里用到了 2个elementUI组件,一个el-dialog,一个el-form。
可以先做el-dialog的配置项,实现visible功能,再去实现el-form的表单展示,一步步完善~

组件源码

<!--
 * @Author: Penk
 * @LastEditors: Penk
 * @LastEditTime: 2022-11-29 18:02:34
 * @FilePath: \front-master\src\components\public\PenkForm.vue
 * @email: 492934056@qq.com
-->
<template>
  <div class="penk-form-container">
    <el-dialog
      v-model="visible1"
      :fullscreen="props.fullscreen"
      :draggable="props.draggable"
      :title="props.title"
      :width="props.width || '50%'"
    >
      <el-form
        ref="ruleFormRef"
        label-width="120px"
        size="large"
        :model="form"
        :rules="props.formConfig.rules"
      >
        <template
          v-for="item in props.formConfig.formItemConfig"
          :key="item.prop"
        >
          <el-form-item
            :label="item.label + ':'"
            :style="{ display: item.hidden == true ? 'none' : '' }"
            :prop="item.prop"
          >
            <!-- 输入框 -->
            <el-input
              v-if="item.type == 'input'"
              v-model="form[item.prop]"
              :placeholder="item.placeholder || ''"
              :clearable="item.clearable"
              :disabled="item.disabled"
              :type="item.inputType"
              :row="item.row"
              :style="{ width: item.width + 'px' }"
            />

            <!-- 下拉框 -->
            <el-select
              v-else-if="item.type == 'select'"
              v-model="form[item.prop]"
              :placeholder="item.placeholder"
              :clearable="item.clearable"
              :disabled="item.disabled"
              :style="{ width: item.width + 'px' }"
            >
              <el-option
                v-for="option in item.data"
                :key="option.value"
                :label="option.label"
                :value="option.value"
                :disabled="option.disabled"
              />
            </el-select>

            <!-- 多选框 -->
            <el-checkbox-group
              v-else-if="item.type == 'checkbox'"
              v-model="form[item.prop]"
              :placeholder="item.placeholder"
              :clearable="item.clearable"
              :disabled="item.disabled"
              :style="{ width: item.width + 'px' }"
            >
              <el-checkbox
                v-for="option in item.data"
                :label="option.value"
                :disabled="option.disabled"
                >{{ option.label }}</el-checkbox
              >
            </el-checkbox-group>

            <!-- 单选框 -->
            <el-radio-group
              v-else-if="item.type == 'radio'"
              v-model="form[item.prop]"
              :placeholder="item.placeholder"
              :clearable="item.clearable"
              :disabled="item.disabled"
              :style="{ width: item.width + 'px' }"
            >
              <el-radio
                :label="option.value"
                size="large"
                v-for="option in item.data"
                >{{ option.label }}</el-radio
              >
            </el-radio-group>
          </el-form-item>
        </template>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="visible1 = false">取消</el-button>
          <el-button type="primary" @click="confirm(ruleFormRef)">
            确定
          </el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, watch, defineEmits, onMounted } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";

let form = reactive({});
const visible1 = ref(true);
const ruleFormRef = ref<FormInstance>();

// 单个form item 的属性
interface formItemConfig {
  // 必填
  // 标签名
  label: string;
  // 对应的属性名 如: from.name ~
  prop: string;
  // 组件所需类型,下拉框或者输入框
  type: string;

  // 选填-通用
  // 宽度
  width?: number;
  // 占位符
  placeholder?: string;
  // 是否清空
  clearable?: boolean;
  // 是否使能
  disabled?: boolean;
  // 是否隱藏
  hidden?: boolean;

  // 选填
  // 特殊组件参数
  // 输入框判断是否文本域
  inputType?: string;
  // 输入框为文本域的时候的行数
  row: number;

  // 选填-特殊
  // 数据,一般是下拉框之类需要可选项的才用到
  data?: any;
}

interface formConfig {
  // 每一项的配置
  formItemConfig: formItemConfig;
  // 规则
  // https://element-plus.gitee.io/zh-CN/component/form.html#自定义校验规则
  rules: any;
}

interface props {
  // 以下是el-form  的配置
  // 编辑时候的formData数据
  formData: any;
  // form配置 包括form item 以及 rules
  formConfig: formConfig;

  // 以下是el-dialog 的配置
  visible: boolean;
  // dialog配置
  width: string | number;
  // 弹框标题
  title: string;
  // 是否为全屏 Dialog
  fullscreen?: boolean;
  // 为 Dialog 启用可拖拽功能
  draggable?: boolean;
  // Dialog 自身是否插入至 body 元素上。 嵌套的 Dialog 必须指定该属性并赋值为 true
  "append-to-body"?: boolean;
  // 是否可以通过点击 modal 关闭 Dialog
  "close-on-click-modal"?: boolean;
}
const props = defineProps<props>();

// 生成事件对象,数组中就是对象名
const emit = defineEmits(["update:visible", "confirm"]);

// 监听父组件的visible,用来简介控制el-dialog的弹框开关,一般是用于开
watch(
  () => props.visible,
  (n, o) => {
    visible1.value = n;
  }
);
// 监听el-dialog显示状态,再通过@update:visible 通知父组件,一般是用于关
watch(visible1, (n, o) => {
  emit("update:visible", n);
});

// 每次触发,就证明父组件点了修改或者添加的按钮,传递了一个新的formData
// 需要重新给form 赋值,并且,对该表单项进行重置
watch(
  () => props.formData,
  (n, o) => {
    resetForm(ruleFormRef.value);
    form = reactive(n);
  }
);

// 确定按钮触发
const confirm = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      emit("confirm", form);
    } else {
      console.log("error submit!", fields);
      ElMessage({
        showClose: true,
        message: "请完善表单信息!",
        type: "error",
      });
    }
  });
};

// 重置表单
const resetForm = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.resetFields();
};

onMounted(() => {
  // 初始化 配置弹框是否可显示
  visible1.value = props.visible;
  // 初始化 配置formData
  form = reactive(props.formData);
});
</script>

<style lang="less" scoped>
.penk-form-container {
  .el-table-border {
    border: 1px #eee solid;
  }
  // :deep(.el-scrollbar__view) {
  //   width: 100%;
  // }
  :deep(.el-dialog__body) {
    border-top: 1px solid #eee;
  }
}
</style>

你可能感兴趣的:(vue组件封装,vue.js,javascript,前端)