Skip to content

一个选择器实现实现高级搜索的展开收起

Posted on:2023年11月6日 at 11:47

image 在表格页面中,我们经常用到带有展开收起功能的过滤表单,

看似很简单的功能,但是实现起来通常不那么优雅。 我们使用grid布局来实现这个就非常简单:

.search-form {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-gap: 10px;

  &:not(.expend) {
    .el-form-item {
      // 前三个和最后一个一直显示,其他的隐藏
      &:nth-child(n + 4):not(:nth-last-child(-n + 1)) {
        display: none;
      }
    }
  }

  .el-form-item {
    margin-bottom: 0;
  }
}

我们把它封装成通用组件

SearchForm.vue

<script setup lang="ts">
  import { computed, ref, useCssModule, watch } from "vue";
  import type { FormInstance, FormProps } from "element-plus";
  import { ArrowDown, ArrowUp } from "@element-plus/icons-vue";

  interface SearchFormProps extends Omit<FormProps, "inline"> {
    column?: number;
  }

  const props = withDefaults(defineProps<SearchFormProps>(), {
    column: 4,
  });

  const emits = defineEmits<{
    toggle: [boolean];
    reset: [];
    confirm: [];
  }>();

  const style = useCssModule();

  const formRef = ref<FormInstance | null>(null);
  const expand = ref(false);

  const formProps = computed(() => {
    const { column, ...formProps } = props;
    return formProps;
  });

  const offset = computed(() => {
    const { column } = props;
    return column + 1;
  });

  const handleToggle = () => {
    expand.value = !expand.value;
    emits("toggle", expand.value);
  };

  const handleReset = () => {
    emits("reset");
  };

  const handleConfirm = () => {
    emits("confirm");
  };

  const styleEl = ref<HTMLStyleElement | null>(null);

  // 这里是因为nth-child选择器不支持传入变量,所以我们选择动态创建css
  const handleGenerateCss = (column: number) => {
    const { searchForm } = style;
    const css = `
    .${searchForm}:not(.expend) .el-form-item:nth-child(n + ${column}):not(:last-child) {
      display: none;
    }
  `;
    const cssText = document.createTextNode(css);
    if (styleEl.value) {
      styleEl.value.removeChild(styleEl.value.firstChild);
      styleEl.value.appendChild(cssText);
    } else {
      const style = document.createElement("style");
      style.appendChild(cssText);
      styleEl.value = style;
      document.head.appendChild(style);
    }
  };

  watch(
    () => props.column,
    column => {
      handleGenerateCss(column);
    },
    { immediate: true }
  );

  defineExpose({
    form: formRef,
  });
</script>

<template>
  <el-form
    ref="formRef"
    v-bind="formProps"
    @submit.prevent="handleConfirm"
    @reset.prevent="handleReset"
    :class="[style.searchForm, { expend: expand }]"
  >
    <slot></slot>
    <el-form-item>
      <el-button @click="handleToggle" :icon="expand ? ArrowUp : ArrowDown">
        {{ expand ? '收起' : '展开' }}
      </el-button>
      <el-button type="primary" native-type="submit">查询</el-button>
      <el-button native-type="reset">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<style module lang="scss">
  .searchForm {
    display: grid;
    grid-template-columns: repeat(v-bind(column), 1fr);
    grid-gap: 10px;

    :global(.el-form-item) {
      margin-bottom: 0;

      &:last-child {
        grid-column-end: v-bind(offset);
      }
    }
  }
</style>

Usage

<script setup lang="ts">
  import { reactive, ref } from "vue";
  import SearchForm from "./Search.vue";

  const formStore = reactive({
    name: "",
    region: "",
    type: "",
    age: "",
    title: "",
    color: "",
    tag: "",
    date: "",
    time: "",
  });
</script>

<template>
  <SearchForm :model="formStore" labelWidth="100px">
    <el-form-item label="name" prop="name">
      <el-input v-model="formStore.name" />
    </el-form-item>
    <el-form-item label="region" prop="region">
      <el-input v-model="formStore.region" />
    </el-form-item>
    <el-form-item label="type" prop="type">
      <el-input v-model="formStore.type" />
    </el-form-item>
    <el-form-item label="age" prop="age">
      <el-input v-model="formStore.age" />
    </el-form-item>
    <el-form-item label="title" prop="title">
      <el-input v-model="formStore.title" />
    </el-form-item>
    <el-form-item label="color" prop="color">
      <el-input v-model="formStore.color" />
    </el-form-item>
    <el-form-item label="tag" prop="tag">
      <el-input v-model="formStore.tag" />
    </el-form-item>
    <el-form-item label="date" prop="date">
      <el-input v-model="formStore.date" />
    </el-form-item>
    <el-form-item label="time" prop="time">
      <el-input v-model="formStore.time" />
    </el-form-item>
    <el-form-item label="time" prop="time">
      <el-input v-model="formStore.time" />
    </el-form-item>
  </SearchForm>
</template>