el-form表格搜索展开折叠组件

在这里插入图片描述
在这里插入图片描述

<template>
  <el-row v-bind="rowProps" type="flex">
    <slot />
    <template v-if="advanced">
      <slot name="advanced" />
    </template>
    <el-col v-bind="actionColOpt">
      <el-form-item class="search-action" label-width="0">
        <el-button v-if="hasAdvanced" type="text" @click="toggleAdvanced">
          {{ advanced ? "收起" : "展开" }}
          <i :class="advanced ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" />
        </el-button>
        <slot name="advanceBefore" />
        <el-button
          plain
          size="small"
          type="primary"
          :loading="searchLoading"
          @click="handleSearch"
        >查询</el-button>
        <el-button plain size="small" @click="handleReset">重置</el-button>
        <slot name="advanceAfter" />
      </el-form-item>
    </el-col>
  </el-row>
</template>
<script setup>
import { computed, useSlots } from 'vue';
import { useBoolean } from '@/hooks';

defineProps({
  rowProps: {
    type: Object,
    default: () => {
      return {
        gutter: 20
      };
    }
  },
  searchLoading: {
    type: Boolean,
    required: true
  }
});

const emit = defineEmits(['reset', 'search']);

const { bool: advanced, toggle: toggleAdvanced } = useBoolean();

const actionColOpt = computed(() => {
  const { default: defalutSlot, advanced: advancedSlot } = useSlots();
  const defaultCol = defalutSlot().reduce(
    (acc, cur) => (acc += cur.componentOptions.propsData.span),
    0
  );
  const advancedCol =
    advanced.value &&
    advancedSlot().reduce((acc, cur) => (acc += cur.componentOptions ? cur.componentOptions.propsData.span : 0), 0);
  /** 满行后余多少份 */
  const redunCol = (defaultCol + (advanced.value ? advancedCol : 0)) % 24;
  /** 没有多余的就占满一行,否则就算出还剩余几份 */
  const actionSpan = redunCol === 0 ? 24 : 24 - redunCol;
  const actionColOpt = {
    span: actionSpan
  };
  return actionColOpt;
});

const hasAdvanced = computed(() => {
  const { advanced: advancedSlot } = useSlots();
  return Boolean(advancedSlot() && advancedSlot().length > 0);
});

function handleReset() {
  emit('reset');
}
function handleSearch() {
  emit('search');
}
</script>
<style lang="scss" scoped>
.el-row {
  flex-wrap: wrap;
}
.el-form {
  width: 100%;
}
:deep(.el-input) {
  width: 100%;
}
:deep(.el-select) {
  width: 100%;
}
:deep(.el-date-editor) {
  width: 100%;
}
:deep(.el-autocomplete) {
  width: 100%;
}
.grid-content {
  border-radius: 4px;
  min-height: 36px;
  background: #d5dce5;
}
.search-action {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
</style>


// 用法

<template>
  <main>
    <el-form label-position="right" label-width="100px">
      <SearchForm>
        <el-col :span="6">
          <el-form-item label="活动名称">
            <el-input v-model="formLabelAlign.name" style="width: 100%" />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="活动名称">
            <el-input v-model="formLabelAlign.name" style="width: 100%" />
          </el-form-item>
        </el-col>
        <el-col :span="6">
          <el-form-item label="活动名称">
            <el-input v-model="formLabelAlign.name" style="width: 100%" />
          </el-form-item>
        </el-col>
        <template #advanced>
          <el-col :span="6">
            <el-form-item label="活动名称">
              <el-input v-model="formLabelAlign.name" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="活动名称">
              <el-input v-model="formLabelAlign.name" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="14">
            <el-form-item label="活动名称">
              <el-input v-model="formLabelAlign.name" style="width: 100%" />
            </el-form-item>
          </el-col>
        </template>
      </SearchForm>
    </el-form>
  </main>
</template>
<script>
export default {
  components: {
    SearchForm: () => import('@/components/TheWelcome.vue')
  },
  data() {
    return {
      formLabelAlign: {
        name: '',
        region: '',
        type: ''
      }
    };
  }
};
</script>

改良版

<template>
  <el-row v-bind="rowProps" type="flex">
    <!-- <slot /> -->
    <template v-for="(item, index) in $slots.default">
      <render-col v-if="advanced ? true : index < showCount" :key="index" :vnodes="item" />
    </template>
    <template v-if="advanced">
      <slot name="advanced" />
    </template>
    <el-col v-bind="actionColOpt">
      <el-form-item class="search-action" label-width="0">
        <el-button v-if="hasAdvanced" type="text" @click="toggleAdvanced">
          {{ advanced ? "收起" : "展开" }}
          <i :class="advanced ? 'el-icon-arrow-up' : 'el-icon-arrow-down'" />
        </el-button>
        <slot name="advanceBefore" />
        <el-button
          plain
          size="small"
          type="primary"
          :loading="searchLoading"
          @click="handleSearch"
        >查询</el-button>
        <el-button plain size="small" @click="handleReset">重置</el-button>
        <slot name="advanceAfter" />
      </el-form-item>
    </el-col>
  </el-row>
</template>
<script setup>
import { computed, useSlots } from 'vue';
import { useBoolean } from '@/hooks';

const props = defineProps({
  rowProps: {
    type: Object,
    default: () => {
      return {
        gutter: 20
      };
    }
  },
  searchLoading: {
    type: Boolean,
    required: true
  },
  alwaysLine: {
    type: Number,
    default: 1
  }
});

const emit = defineEmits(['reset', 'search']);

/** 渲染vnode对象组件 */
const RenderCol = {
  functional: true,
  render: (h, ctx) => ctx.props.vnodes
};

const slots = useSlots();
const defalutSlot = slots.default();
const advancedSlot = slots.advanced();

const { bool: advanced, toggle: toggleAdvanced } = useBoolean();
/** 最大能显示的份数 */
const maxCol = 24 * props.alwaysLine;
/** 常显的个数 */
const showCount = computed(() => {
  let total = 0;
  for (const index in defalutSlot) {
    total += defalutSlot[index].componentOptions.propsData.span;
    if (total > maxCol) {
      return index - 1;
    }
  }
  return defalutSlot.length;
});

const hasAdvanced = computed(() => {
  if (showCount.value < defalutSlot.length) return true;
  return Boolean(advancedSlot && advancedSlot.length > 0);
});

const actionColOpt = computed(() => {
  let defaultCol = 0;
  const length = advanced.value ? defalutSlot.length : showCount.value;
  for (let i = 0; i < length; i++) {
    defaultCol += defalutSlot[i].componentOptions.propsData.span;
  }
  const advancedCol =
    advanced.value && advancedSlot?.length > 0 &&
    advancedSlot.reduce((acc, cur) => (acc += cur.componentOptions ? cur.componentOptions.propsData.span : 0), 0);
  /** 满行后余多少份 */
  const redunCol = (defaultCol + (advanced.value ? advancedCol : 0)) % 24;
  /** 没有多余的就占满一行,否则就算出还剩余几份 */
  const actionSpan = redunCol === 0 ? 24 : 24 - redunCol;
  const actionColOpt = {
    span: actionSpan
  };
  return actionColOpt;
});

function handleReset() {
  emit('reset');
}
function handleSearch() {
  emit('search');
}
</script>
<style lang="scss" scoped>
.el-row {
  flex-wrap: wrap;
}
.el-form {
  width: 100%;
}
:deep(.el-input) {
  width: 100%;
}
:deep(.el-select) {
  width: 100%;
}
:deep(.el-date-editor) {
  width: 100%;
}
:deep(.el-autocomplete) {
  width: 100%;
}
.grid-content {
  border-radius: 4px;
  min-height: 36px;
  background: #d5dce5;
}
.search-action {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
</style>
![在这里插入图片描述](https://img-blog.csdnimg.cn/4e80289aef4841eb8a0d467d192510d9.png#pic_center)

全新UI版本
在这里插入图片描述
在这里插入图片描述

<template>
  <div class="search-advanced">
    <el-collapse v-model="name" accordion>
      <el-collapse-item name="1">
        <template slot="title">
          <div class="search-advanced-top">
            <div class="top-left">
              <svg-icon :icon-class="name === '1' ? 'icon-down' : 'icon-right'" />
              <span>筛选</span>
            </div>
            <!-- 点击事件需加.stop -->
            <slot name="search-action" />
          </div>
        </template>
        <div class="search-advanced-content">
          <el-row v-bind="rowProps" type="flex">
            <slot />
            <el-col v-bind="actionColOpt">
              <el-form-item class="search-action" label-width="0">
                <slot name="advanceBefore" />
                <el-button
                  plain
                  size="small"
                  type="primary"
                  :loading="searchLoading"
                  @click="handleSearch"
                >查询</el-button
                >
                <el-button plain size="small" @click="handleReset">重置</el-button>
                <slot name="advanceAfter" />
              </el-form-item>
            </el-col>
          </el-row>
        </div>
      </el-collapse-item>
    </el-collapse>
  </div>
</template>

<script setup>
import { ref, computed, useSlots } from 'vue';
import { useWindowSize } from '@vueuse/core';

defineProps({
  rowProps: {
    type: Object,
    default: () => {
      return {
        gutter: 0
      };
    }
  },
  searchLoading: {
    type: Boolean,
    required: true
  }
});

const emit = defineEmits(['reset', 'search']);

const name = ref('1');
const slots = useSlots();
const defalutSlot = slots.default();
const { width } = useWindowSize();

const actionColOpt = computed(() => {
  const points = getBreakpoints(width.value);
  let defaultCol = 0;
  const length = defalutSlot.length;
  for (let i = 0; i < length; i++) {
    const propsData = defalutSlot[i].componentOptions.propsData;
    defaultCol += propsData[points] ? propsData[points] : propsData['span'];
  }
  /** 满行后余多少份 */
  const redunCol = defaultCol % 24;
  /** 没有多余的就占满一行,否则就算出还剩余几份 */
  const actionSpan = redunCol === 0 ? 24 : 24 - redunCol;
  const actionColOpt = {
    span: actionSpan
  };
  return actionColOpt;
});
/**
 * 获取响应式栅格对应断点(和el-col保持一致)
 * @param {number} width
 */
function getBreakpoints(width) {
  let breakPoint = 'span';
  switch (!!width) {
    case width < 768:
      breakPoint = 'xs';
      break;
    case width >= 768 && width < 992:
      breakPoint = 'sm';
      break;
    case width >= 992 && width < 1200:
      breakPoint = 'md';
      break;
    case width >= 1200 && width < 1920:
      breakPoint = 'lg';
      break;
    case width >= 1920:
      breakPoint = 'xl';
      break;
  }
  return breakPoint;
}

function handleReset() {
  emit('reset');
}
function handleSearch() {
  emit('search');
}
</script>
<style lang="scss" scoped>
.search-advanced {
  margin: 0 0 24px 0;
  background: #ffffff;
  .search-advanced-top {
    width: 100%;
    height: 39px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #f4f4f4;
    padding: 0 20px 0 8px;
    .top-left {
      display: flex;
      align-items: center;
      cursor: pointer;
      :deep(.svg-icon) {
        width: 20px;
        height: 20px;
      }
      span {
        font-size: 14px;
        font-weight: 500;
        color: #333333;
        margin-left: 6px;
      }
    }
  }
  .search-advanced-content {
    padding: 24px 20px 0 0;
  }
  :deep(.el-collapse) {
    border: none;
    .el-collapse-item__wrap {
      border: none;
    }
    .el-collapse-item__content {
      padding-bottom: 0;
    }
    .el-collapse-item__header {
      height: auto;
      border: none;
      line-height: 0;
    }
    .el-collapse-item__arrow {
      display: none;
    }
  }
  :deep(.el-form-item__label) {
    padding: 0 8px 0 0;
    font-size: 13px;
  }

  .el-row {
    flex-wrap: wrap;
  }
  .el-form {
    width: 100%;
  }
  :deep(.el-input) {
    width: 100%;
  }
  :deep(.el-select) {
    width: 100%;
  }
  :deep(.el-date-editor) {
    width: 100%;
  }
  :deep(.el-autocomplete) {
    width: 100%;
  }
  .grid-content {
    border-radius: 4px;
    min-height: 36px;
    background: #d5dce5;
  }
  .search-action {
    display: flex;
    align-items: center;
    justify-content: flex-end;
  }
}
</style>

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值