<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>