基于 Element Plus 封装查询条件表单组件(多条件折叠展开)

2,836 阅读3分钟

场景介绍

  1. 后台管理系统有很多功能都需要基于 Element Plus UI 库进行二次封装,以便于简化我们后续的开发。
  2. 查询条件表单是我们系统中非常常见的功能,我们需要把它封装成一个通用的组件,方便在系统开发中提升开发效率。
  3. 除了在实现基本查询条件的功能上,还需要实现多条件的折叠和展开功能
  4. 下面的代码实现是 基于 Vue3 + Typescript + Element Plus实现的,仅供参考

示例效果

  1. 查询条件收起的状态 image.png
  2. 查询条件展开的状态 image.png

实现代码

  1. 封装查询条件表单组件 SearchForm.vue

    <template>
      <div v-if="showSearchBtn" class="search-form-box">
        <el-form :inline="true" :model="searchForm" size="default" ref="searchFormRef">
          <el-row>
            <el-col :span="cond.span" v-for="cond in sliceCodList" :key="cond.id">
              <el-form-item :label="cond.label + ':'" :prop="cond.prop" class="flex-form-item">
                <!-- 输入框 -->
                <el-input v-if="cond.type === 'input'" v-model="searchForm[cond.prop]" :placeholder="cond.placeholder"
                  clearable />
                <!-- 下拉框 -->
                <el-select v-else-if="cond.type === 'select'" v-model="searchForm[cond.prop]"
                  :placeholder="cond.placeholder" clearable>
                  <el-option v-for="op in cond.optionList" :key="op.value" :label="op.label" :value="op.value" />
                </el-select>
                <!-- 日期组合 -->
                <el-date-picker v-else-if="cond.type === 'daterange'" v-model="searchForm[cond.prop]" type="daterange"
                  start-placeholder="开始日期" end-placeholder="结束日期" />
                <!-- 日期时间 -->
                <el-date-picker v-else-if="cond.type === 'datetime'" v-model="searchForm[cond.prop]" type="datetime"
                  placeholder="请选择日期时间" style="width: 100%" />
              </el-form-item>
            </el-col>
            <el-col :span="rSpanCount" class="el-col-wrapper">
              <el-form-item class="btn-group-item flex-end">
                <el-button type="primary" :icon="Search" @click="search">查询</el-button>
                <el-button plain :icon="RefreshRight" @click="reset">重置</el-button>
                <el-button v-show="showFoldBtn" type="primary" link @click="toggleFold">
                  {{ conditionFold ? "展开" : "收起" }}
                  <el-icon v-if="conditionFold">
                    <ArrowDown />
                  </el-icon>
                  <el-icon v-else>
                    <ArrowUp />
                  </el-icon>
                </el-button>
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </div>
    </template>
    
    <script lang="ts" setup name="searchForm">
    import {
      Search,
      RefreshRight,
      ArrowDown,
      ArrowUp,
    } from "@element-plus/icons-vue";
    import { reactive, ref, computed, onMounted } from "vue";
    
    // 查询条件的类型接口
    interface SearchFormInterFace {
      [key: string]: string | number | [];
    };
    
    /**
     * updateTableList 组件外部传入的拉取表格数据的方法
     * conditionList   组件外部传入的查询条件的配置列表数据
     */
    const props = defineProps([
      "updateTableList",
      "conditionList",
    ]);
    
    // 查询条件组件的实例
    const searchFormRef = ref();
    
    // 切换展开和收起查询条件
    const conditionFold = ref(true);
    
    const toggleFold = () => {
      conditionFold.value = !conditionFold.value;
    };
    
    // 初始化折叠查询条件的断点,从第几个查询条件开始(默认是从第3个,因为默认配置的span值是6)
    const initConditionFoldLen = 3;
    
    // 展示右侧按钮组(折叠||收起按钮)
    const showFoldBtn = computed(() => {
      return props.conditionList.length > initConditionFoldLen;
    });
    
    // 展示查询条件组件
    const showSearchBtn = computed(() => {
      return props.conditionList.length > 0;
    });
    
    // 右侧按钮组动态计算的span值
    const rSpanCount = computed(() => {
      let totalSpan = 0;
      if (initConditionFoldLen > 0) {
        if (!conditionFold.value) {
          totalSpan = props.conditionList.reduce((prev: number, next: any) => {
            return prev + next.span
          }, 0)
        } else {
          const sliceCondList = props.conditionList.slice(0, initConditionFoldLen);
          totalSpan = sliceCondList.reduce((prev: number, next: any) => {
            return prev + next.span
          }, 0)
        }
      } else {
        totalSpan = props.conditionList.reduce((prev: number, next: any) => {
          return prev + next.span
        }, 0)
      }
      return 24 - (totalSpan % 24);
    })
    
    // 打印计算出来的,右侧按钮组占的span值,再通过flex布局,flex-end 定位到最右边
    console.log(rSpanCount.value, 'rSpanCount@@@');
    
    // 初始化查询条件的列表
    const sliceCodList = computed(() => {
      return props.conditionList.slice(
        0,
        conditionFold.value ? initConditionFoldLen : props.conditionList.length
      );
    });
    
    // 查询条件表单数据
    const searchForm = reactive<SearchFormInterFace>({});
    
    // 搜索表单
    const search = () => {
      props.updateTableList({
        pageSize: 10,
        currentPage: 1,
        ...searchForm,
      });
    };
    // 重置搜索
    const reset = () => {
      searchFormRef.value.resetFields();
      props.updateTableList({
        pageSize: 10,
        currentPage: 1,
        ...searchForm,
      });
    };
    
    // 初始化查询条件的值
    onMounted(() => {
      props.conditionList.forEach((cond: any) => {
        searchForm[cond.prop] = cond.type === "datetimerange" ? [] : "";
      });
    });
    </script>
    
    <style lang="scss" scoped>
    .search-form-box {
      box-sizing: border-box;
      box-shadow: var(--el-box-shadow-light);
      background-color: #fff;
      border-radius: 4px;
      padding: 12px;
      margin-bottom: 12px;
    
      .flex-form-item {
        display: flex;
        align-items: center;
      }
    
      .el-col-wrapper {
        display: flex;
    
        .btn-group-item {
          flex: 1;
          display: flex;
    
          :deep(.el-form-item__content) {
            flex: 1;
            display: flex;
            align-items: center;
          }
    
          &.flex-end {
            :deep(.el-form-item__content) {
              justify-content: flex-end;
            }
          }
        }
      }
    }
    </style>
    
  2. 使用查询条件表单组件(引入组件即可)

    <template>
        <SearchForm
            :updateTableList="updateTableList"
            :conditionList="conditionList"
        />
    </template>
    <script lang="ts" setup>
    import { reactive, ref } from "vue";
    // 定义表格数据每项的类型
    type tableDataItem = {
      username: string;
      age: number;
      address: string;
      big: string;
      color: string;
      hobby: string;
      sex: string;
      school: string;
      grade: string;
    };
    // 表格加载loading
    const loading = ref(false);
    // 表格数据
    let tableData = ref<tableDataItem[]>([]);
    // 表格数据总条数
    const total = ref(0);
    // 查询条件配置项
    // type 是查询条件里面的组件类型 如input select等,可以自行添加需要的组件
    const conditionList = reactive([
      {
        id: 1,
        type: "input",
        label: "用户名称",
        prop: "username",
        placeholder: "请输入用户名称",
        span: 6,
      },
      {
        id: 2,
        type: "select",
        label: "个头大小",
        prop: "big",
        placeholder: "请选择个头大小",
        optionList: [
          {
            label: "大",
            value: 1,
          },
          {
            label: "小",
            value: 0,
          },
        ],
        span: 6,
      },
      {
        id: 3,
        type: "select",
        label: "性别",
        prop: "sex",
        placeholder: "请选择性别",
        optionList: [
          {
            label: "男",
            value: 1,
          },
          {
            label: "女",
            value: 0,
          },
        ],
        span: 6,
      },
      {
        id: 4,
        type: "input",
        label: "地址",
        prop: "address",
        placeholder: "请输入地址",
        span: 6,
      },
      {
        id: 5,
        type: "datetime",
        label: "档案时间",
        prop: "time",
        span: 6,
      },
    ]);
    // 请求数据代码仅供参考,这里放自己的业务逻辑
    const updateTableList = async (reqParams: any) => {
      loading.value = true;
      const { code, data }: any = await reqTableList(reqParams);
      if (code === 200) {
        tableData.value = data.list.map((item: tableDataItem) => ({
          ...item,
          bigLabel: item.big ? "大" : "小",
          sexLabel: item.sex ? "男" : "女",
        }));
        total.value = data.total;
        setTimeout(() => {
          loading.value = false;
        }, 1000);
      }
    };
    </script>