Vue3+ts+element-plus 组件的二次封装-- 头部搜索条件的封装

Vue 常用笔记

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

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

Vue3+ts+element-plus 组件的二次封装-- 头部搜索条件的封装_第1张图片


组件库开发流程

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

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

文章目录

  • Vue 常用笔记
  • 组件库开发流程
  • 前言
  • 头部搜索栏
    • 1. 效果图
    • 2. 父组件
      • 2.1 父组件 template 中的调用
      • 2.2. 父组件 script 中的调用
        • 2.2.1 获取搜索组件中的查询对象queryObj
        • 2.2.2 使用的配置
    • 3. 组件的封装
      • 3.1 封装组件template
        • 3.1.1 请求组件的配置
        • 3.1.2 预留的插槽
      • 3.2 封装组件script
        • 3.2.1 props属性的配置
        • 3.2.2 组件内的queryObj变量
  • 总结
  • 源码


前言

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

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

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

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

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

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

效果图如下:

在这里插入图片描述

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


头部搜索栏

原理:通过传递配置文件searchConfig(数组),遍历里面的数据,每一项都是一个element-UI组件。等需要触发搜索请求的时候,通过ref 获取到该组件的实例以及所需的searchData。

1. 效果图

Vue3+ts+element-plus 组件的二次封装-- 头部搜索条件的封装_第2张图片


2. 父组件

2.1 父组件 template 中的调用


 <PenkSearch ref="refPenkSearch" :searchConfig="searchConfig">
   <el-button type="primary" @click="getList">搜索el-button>
   <el-button @click="handleAddItem">新增el-button>
 PenkSearch>

可以看到,只有简单的searchConfig配置,就可以了,其中2个button,是通过插槽默认是在搜索后面追加,样式也在组件中编码了,可以省掉很多步骤。


2.2. 父组件 script 中的调用

2.2.1 获取搜索组件中的查询对象queryObj

调用的时候就是根据获取的ref实例,使用暴露出来的对象里面的数据即可,主要代码就是下面两句…

const refPenkSearch = ref();
…refPenkSearch.value.queryObj

// 搜索框ref实例
const refPenkSearch = ref();
// 表格数据
let tableData = reactive([]);
// 查找数据
async function getList() {
  // 清空数据
  tableData.length = 0;
  console.log("tableData1:", tableData);

  // 判断是否有对象,没有的话就自动弄
  let res = await http.getList({
    ...refPenkSearch.value.queryObj,
    pageNum: paginationData.pageNum,
    pageSize: paginationData.pageSize,
  });
  // @ts-ignore
  tableData.push(...res.rows);
  paginationData.total = res.count;

  console.log("tableData:", tableData);
}

tips:这边要注意reactive 创建的tabelData,不能使用直接赋值,因为是个proxy对象~


2.2.2 使用的配置

这边有的配置是可填可不填的,具体的配置项可以看封装组件里面的参数,这边大概看一下

  • type:类型,判断是什么UI组件
  • prop:很关键的,用来绑定该UI组件对应属性名!!!
  • data:用来存放可选的UI组件的配置,比如下拉框,级联之类...
  • label:在UI组件前面的label
  • width:UI组件的宽度
  • hidden:是否隐藏,有些业务需求是根据不同角色,一些是不展示的
  • clearabel:是否可清空数据
 const searchConfig = [
  {
    type: "select",
    label: "父级用户类型",
    width: 150,
    prop: "parentId",
    hidden: false,
    clearable: true,
    data: [],
  },
  {
    type: "input",
    label: "类型名",
    placeholder: "请输入类型名",
    width: 150,
    clearable: true,
    prop: "typeName",
    hidden: false,
  },
  {
    type: "select",
    label: "是否查找软删除",
    placeholder: "请选择",
    width: 150,
    prop: "paranoid",
    hidden: false,
    clearable: true,
    data: [
      {
        value: 0,
        label: "是",
      },
      {
        value: 1,
        label: "否",
      },
    ],
  },
];

3. 组件的封装

3.1 封装组件template

3.1.1 请求组件的配置
  1. searchConfig:是个对象数组,用于遍历出一个个请求需要的组件,目前只列出了input跟select,可按需增加。
  2. searchConfig.data:用来给那些可选的组件,比如select或者级联…
  3. queryObj:是封装组件内的一个reactive对象,并且通过defineExpose 暴露给了父组件。
  4. searchConfig.props是每一项的键名,queryObj通过字符串索引的方式,将至设置成键名,并且通过v-model将键值绑定到该每一项的UI组件上。
<template v-for="item in props.searchConfig" :key="item.prop">
  <div
    class="penk-search-item"
    v-if="item.hidden != true"
  >
    <span>{{ item.label }}:span>
    
    <el-input
      v-if="item.type == 'input'"
      v-model="queryObj[item.prop]"
      :placeholder="item.placeholder || ''"
      :clearable="item.clearable"
      :disabled="item.disabled"
      :style="{ width: item.width + 'px' }"
    />
    
    <el-select
      v-else-if="item.type == 'select'"
      v-model="queryObj[item.prop]"
      :placeholder="item.placeholder || ''"
      :clearable="item.clearable"
      :disabled="item.disabled"
      filterable
      :style="{ width: item.width + 'px' }"
    >
      <el-option
        v-for="option in item.data"
        :key="option.value"
        :label="option.label"
        :value="option.value"
      />
    el-select>
  div>
template>

3.1.2 预留的插槽

主要用于存放按钮,比如添加,查询按钮…


<div class="penk-search-footer">
  <slot>slot>
div>

3.2 封装组件script

3.2.1 props属性的配置

由于使用了TS来编码,咋们可以在组件中interface中写清楚一些配置,方便使用者知道有什么配置 :)

// 这边主要是申明了是个对象数组,并且每个对象定义是按照searchItem这个接口来的
interface Props {
  searchConfig: searchItem[];
}
// 每一个搜索条件对应的一些参数
interface searchItem {
  // 必填
  // 标签名
  label: string;
  // 组件所需类型,下拉框或者输入框
  type: string;
  // 对应的属性名 如: queryObj.name ~
  prop: string;

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

  // 选填-特殊
  // 数据,一般是下拉框之类需要可选项的才用到
  data?: any;
}
3.2.2 组件内的queryObj变量
interface queryObj {
  [index: string]: any;
}

// queryObj 就是一个搜索条件的对象,里面每一个属性就是一个搜索条件
// 设置私有属性,防止被修改~
const queryObj = reactive<queryObj>({});

// 将queryObj暴露出去,父组件才可以调用
defineExpose({
  queryObj,
});

总结

  1. 封装很简单,关联性不强,通过searchConfig传递给子组件,就可以生成相应的头部搜索栏视图。
  2. 搜索栏的操作,只会同步到组件内的queryObj对象。
  3. 父组件想要获取queryObj的时候,只需通过ref获取即可…

当然,你也可以实现双向绑定的功能,但是每一个父组件,是不是都要声明一个变量呢?这个变量好像也没什么其他的用处了...


源码

<!--
 * @Author: Penk
 * @LastEditors: Penk
 * @LastEditTime: 2022-11-29 13:31:21
 * @FilePath: \front-master\src\components\public\PenkSearch.vue
 * @email: 492934056@qq.com
-->
<template lang="">
  <div class="penk-search-box">
    <!-- 头部插槽 -->
    <slot name="header"></slot>
    <template v-for="item in searchConfig" :key="item.prop">
      <div
        class="penk-search-item"
        :style="{ display: item.hidden == true ? 'none' : '' }"
      >
        <span>{{ item.label }}</span>
        <!-- 输入框 -->
        <el-input
          v-if="item.type == 'input'"
          v-model="queryObj[item.prop]"
          :placeholder="item.placeholder || ''"
          :clearable="item.clearable"
          :disabled="item.disabled"
          :style="{ width: item.width + 'px' }"
        />
        <!-- 下拉框 -->
        <el-select
          v-else-if="item.type == 'select'"
          v-model="queryObj[item.prop]"
          :placeholder="item.placeholder || ''"
          :clearable="item.clearable"
          :disabled="item.disabled"
          filterable
          :style="{ width: item.width + 'px' }"
        >
          <el-option
            v-for="option in item.data"
            :key="option.value"
            :label="option.label"
            :value="option.value"
          />
        </el-select>
      </div>
    </template>

    <!-- 默认尾部插槽 -->
    <div class="penk-search-footer">
      <slot></slot>
    </div>
  </div>
</template>

<script setup lang="ts">
import { reactive } from "vue";

// 每一个搜索条件的对象
interface queryObj {
  [index: string]: any;
}

// 申明是个搜索条件的对象
interface searchItem {
  // 必填
  // 标签名
  label: string;
  // 组件所需类型,下拉框或者输入框
  type: string;
  // 对应的属性名 如: queryObj.name ~
  prop: string;

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

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

interface Props {
  searchConfig: searchItem[];
}

// 定义props
let props = defineProps<Props>();

// 设置私有属性,防止被修改~
const queryObj = reactive<queryObj>({});

// 将queryObj暴露出去,父组件才可以调用
defineExpose({
  queryObj,
});
</script>

<style lang="less" scoped>
.penk-search-box {
  margin-bottom: 10px;
  display: flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: wrap;

  .penk-search-item {
    margin-right: 20px;
    margin-bottom: 10px;
  }

  .penk-search-footer {
    margin-bottom: 10px;
    & > {
      margin-right: 20px;
    }
  }
}
</style>


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