在表格页面中,我们经常用到带有展开收起功能的过滤表单,
看似很简单的功能,但是实现起来通常不那么优雅。
我们使用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>